ROS2 - Publisher Subscriber Project
Let's put everything we have gone through so far into creating some interesting projects. A basic knowledge of linux terminal and python programming language is required for completing the projects. In all the code snippets provided, I have given comments wherever necessary. Consider this as a general guide and feel free to modify the code as per your requirements.
We are going to write a simple publisher - subscriber program for finding the sum of n positive numbers. The program takes a positive integer as input and publishes it via a topic in a publisher node. Another subscriber node subscribes to the topic having the input data, calculates the sum of integers until the input number and displays it in the terminal.
Create and set up a ROS2 workspace folder named ros2_ws and a src folder inside it.
$ mkdir -p ~/ros2_ws/src
$ cd ~/ros2_ws
From the root of your workspace (ros2_ws), we can now build your packages using the command
$ colcon build
We can see that colcon has created some new directories.
Let’s keep in mind that the ‘install’ directory is where our workspace’s setup files will be present.
To enable ROS2 commands, source the ROS2 setup file.
$ source /opt/ros/humble/setup.bash
Create a package called ‘sum_nums’ for our project and use the ament python build system. If the project has multiple packages in the future, putting all the relevant project packages in a workspace is very important because you can build multiple packages at once using the colcon build in the workspace root. Else, all the packages needs to be built individually.
$ cd src
$ ros2 pkg create --build-type ament_python --license Apache-2.0 sum_nums
This will automatically generate the necessary files.
If we see the directory structure of our src folder, it will look something like this.
sum_nums/
├── LICENSE
├── package.xml
├── resource
│ └── sum_nums
├── setup.cfg
├── setup.py
├── sum_nums
│ └── __init__.py
└── test
├── test_copyright.py
├── test_flake8.py
└── test_pep257.py
The relevant files are the following:
- package.xml -> containing meta information about the package.
- resource/sum_nums -> marker file for the package.
- setup.cfg -> required when a package has executables, so ros2 run can find them.
- setup.py -> contains instructions for how to install the package.
- sum_nums - directory with the same name as the package, used by ROS 2 tools to find the package, and contains __init__.py file.
We will create two python ROS2 node files named ‘sum_nums_publisher.py’ and ‘sum_nums_subscriber.py’. The sum_nums_publisher.py node will accept a number from the user and publish it to a topic named ‘input_number’. The sum_nums_subscriber.py node subscribes to the topic ‘input_number’, calculates the sum of n numbers up to the input number and displays the final sum.
Create a python ROS2 publisher node file named ‘sum_nums_publisher.py’ inside ‘sum_nums/sum_nums’ folder, adjacent to where the file __init.py__ is present.
$ cd sum_nums/sum_nums
$ touch sum_nums_publisher.py sum_nums_subscriber.py
Open ‘sum_nums_publisher.py’ and populate it with the publisher code:
import rclpy
from rclpy.node import Node
from std_msgs.msg import Int8
class SumNumsPub(Node):
"""
Node to publish positive integer numbers.
"""
def __init__(self):
"""
Initializes the SumNumsPub class.
"""
super().__init__('sum_nums_publisher_node')
# Create a publisher for data type Int8
self.publisher_ = self.create_publisher(Int8, 'input_number', 10)
def sum_nums_publish(self, data):
"""
Publishes the given data.
Parameters:
data: Data to be published.
"""
self.publisher_.publish(data)
def main(args=None):
"""
Main function to initialize the node and publish a positive integer number.
"""
rclpy.init(args=args)
sum_nums_publisher_node = SumNumsPub()
num_data = Int8()
# Accept a keyboard input
num_input = int(input("Enter a positive integer number: "))
if num_input < 0:
print("Input number is not positive")
else:
print('Entered:', num_input)
num_data.data = num_input
# Publish the integer to the topic
sum_nums_publisher_node.sum_nums_publish(num_data)
# Destroy the node explicitly
sum_nums_publisher_node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Open ‘‘sum_nums_subscriber.py’ and populate it with the subscriber code:
import rclpy
from rclpy.node import Node
from std_msgs.msg import Int8
class SumNumsSub(Node):
"""
Node to subscribe to a topic and compute the sum of first n integer numbers.
"""
def __init__(self):
"""
Initializes the SumNumsSub class.
"""
super().__init__('sum_nums_publisher_node')
# Create a subscription to the 'input_number' topic
self.subscription = self.create_subscription(
Int8,
'input_number',
self.subscriber_callback,
10)
self.subscription # prevent unused variable warning
self.num = 0
self.sum = 0
def subscriber_callback(self, msg):
"""
Callback function to compute the sum of first n integer numbers.
Parameters:
msg: The received message containing the number n.
"""
self.num = int(msg.data)
self.sum = self.recursive_sum(self.num)
print("Sum of first", self.num, "integer numbers is:", self.sum)
def recursive_sum(self, n):
"""
Recursive function to compute the sum of first n integer numbers.
Parameters:
n: The number of integer numbers to sum.
Returns:
The sum of the first n integer numbers.
"""
if n <= 1:
return n
else:
return n + self.recursive_sum(n - 1)
def main(args=None):
"""
Main function to initialize the node and spin it to handle messages.
"""
rclpy.init(args=args)
sum_nums_subscriber_node = SumNumsSub()
rclpy.spin(sum_nums_subscriber_node)
# Destroy the node explicitly
sum_nums_subscriber_node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
We need to configure additional files in the package to enable the node executables to be setup and built correctly. We need to add our dependencies to package.xml file and the node entry points to setup.py file inside the package root folder.
Modify the package.xml, populate the <description>, <maintainer> and <license> tags and after those lines, add the following dependencies that we imported in our sum_nums_publisher.py file and save the file.
<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>
Modify the setup.py file, matching the maintainer, maintainer_email, description and license fields to your package.xml. Then Add the following line within the console_scripts bracket of the entry_points field and save it:
'sum_nums_pub = sum_nums.sum_nums_publisher:main',
'sum_nums_sub = sum_nums.sum_nums_subscriber:main',
Here 'sum_nums_pub’ and ‘sum_nums_sub’ are the names of our executables.
Also check the setup.cfg file to see if it’s populated automatically. The settings in setup.cfg file will put the executables in install/sum_nums/lib/sum_nums/ folder, allowing ‘ros2 run’ command to access them.
Return to the root of our workspace, build our project and source the installation folder to add it to the path for finding the new executables.
$ cd ~/ros2_ws
$ colcon build
$ source install/setup.bash
Once the build is done, let’s try to run our node executables.
In a same terminal, run the sum nums subscriber node.
$ ros2 run sum_nums sum_nums_sub
In a new terminal, run the sum nums publisher node.
$ cd ~/ros2_ws
$ source install/setup.bash
$ ros2 run sum_nums sum_nums_pub
Enter a positive integer and press Enter.
The sum will be displayed by the sum_nums_sub node in the first terminal.
NOTE: You can publish to the topic ‘input_number’ manually using ‘ros2 topic echo’ command, however this tutorial is meant to get a grasp on the basics by covering all the areas that we went through in the previous blogs.