In this post, we will learn how to create a basic publisher node and a subscriber node in ROS 2 Foxy Fitzroy using Python. You can think of a node as a small single-purpose program within a larger robotic system. Publisher nodes publish data, subscriber nodes receive data, and a publishing subscriber node receives data and publishes data.
The official tutorial is located in the ROS 2 Foxy documentation, but we’ll run through the entire process step-by-step below.
You Will Need
In order to complete this tutorial, you will need:
Prerequisites
You have already created a workspace.
Create a Package
Open a new terminal window, and navigate to the src directory of your workspace:
cd ~/dev_ws/src
Now let’s create a package named py_pubsub.
Type this command:
ros2 pkg create --build-type ament_python py_pubsub
Your package named py_pubsub has now been created.
Write a Publisher Node
Move to the /dev_ws/src/py_pubsub/py_pubsub folder. This is where your Python code will go for your publisher and subscriber.
cd py_pubsub/py_pubsub
Make sure you have a text editor installed. I like to use gedit.
sudo apt-get install gedit
Create a blank Python file called my_publisher_node.py.
gedit my_publisher_node.py
Write the following code. Don’t be intimidated. Just go one line at a time and read the comments to understand what each line does.
This publisher node publishes the message “Hi Automatic Addison!” every 500 milliseconds (i.e. two times per second) to a topic named addison.
# ROS Client Library for Python
import rclpy
# Handles the creation of nodes
from rclpy.node import Node
# Enables usage of the String message type
from std_msgs.msg import String
class MinimalPublisher(Node):
"""
Create a MinimalPublisher class, which is a subclass of the Node class.
"""
def __init__(self):
"""
Class constructor to set up the node
"""
# Initiate the Node class's constructor and give it a name
super().__init__('minimal_publisher')
# Create the publisher. This publisher will publish a String message
# to the addison topic. The queue size is 10 messages.
self.publisher_ = self.create_publisher(String, 'addison', 10)
# We will publish a message every 0.5 seconds
timer_period = 0.5 # seconds
# Create the timer
self.timer = self.create_timer(timer_period, self.timer_callback)
# Initialize a counter variable
self.i = 0
def timer_callback(self):
"""
Callback function.
This function gets called every 0.5 seconds.
"""
# Create a String message
msg = String()
# Set the String message's data
msg.data = 'Hi Automatic Addison: %d' % self.i
# Publish the message to the topic
self.publisher_.publish(msg)
# Display the message on the console
self.get_logger().info('Publishing: "%s"' % msg.data)
# Increment the counter by 1
self.i += 1
def main(args=None):
# Initialize the rclpy library
rclpy.init(args=args)
# Create the node
minimal_publisher = MinimalPublisher()
# Spin the node so the callback function is called.
rclpy.spin(minimal_publisher)
# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
minimal_publisher.destroy_node()
# Shutdown the ROS client library for Python
rclpy.shutdown()
if __name__ == '__main__':
main()
Note that in this tutorial, we are publishing a string to a topic. In a robotics project, you’ll typically be publishing numerical values. We’re just using strings in this example as a demonstration.
Add Dependencies
Now that we’ve written our publisher node, we need to let our system know what libraries our node needs in order to execute properly. These libraries are our node’s dependencies.
Go to the dev_ws/src/py_pubsub folder, and see what files are in there.
cd ~/dev_ws/src/py_pubsub
dir
Here are the files you should see.
The package.xml file contains key information about the py_pubsub package.
Open the package.xml file.
gedit package.xml
Fill in the description of the cpp_pubsub package, your email address and name on the maintainer line, and the license you desire (e.g. Apache License 2.0).
<description>A minimal publisher/subscriber that uses the ROS Python Client Library</description>
<maintainer email="automaticaddison@todo.todo">automaticaddison</maintainer>
<license>Apache License 2.0</license>
Now, after the <build_type>ament_cmake</build_type> line, add the two dependencies your node needs in order to compile.
<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>
Save the file and close it to return to the terminal window.
Modify Setup.py
Open setup.py.
gedit setup.py
Set the following fields.
maintainer='automaticaddison',
maintainer_email='automaticaddison@todo.todo',
description='A minimal publisher/subscriber that uses the ROS Python Client Library',
license='Apache License 2.0',
Add the following line within the console_scripts brackets of the entry_points field:
entry_points={
'console_scripts': [
'my_publisher = py_pubsub.my_publisher_node:main',
],
},
Save and close the file.
Write a Subscriber Node
Now let’s write a subscriber node. This node will subscribe to String messages that are published by the publisher node to the addison topic.
Navigate to the dev_ws/src/py_pubsub/py_pubsub directory.
cd ~/dev_ws/src/py_pubsub/py_pubsub
Create a blank Python file called my_subscriber_node.py.
gedit my_subscriber_node.py
Write the following code.
# ROS Client Library for Python
import rclpy
# Handles the creation of nodes
from rclpy.node import Node
# Handles string messages
from std_msgs.msg import String
class MinimalSubscriber(Node):
"""
Create a subscriber node
"""
def __init__(self):
# Initiate the Node class's constructor and give it a name
super().__init__('minimal_subscriber')
# The node subscribes to messages of type std_msgs/String,
# over a topic named: /addison
# The callback function is called as soon as a message is received.
# The maximum number of queued messages is 10.
self.subscription = self.create_subscription(
String,
'addison',
self.listener_callback,
10)
self.subscription # prevent unused variable warning
def listener_callback(self, msg):
# Display a message on the console every time a message is received on the
# addison topic
self.get_logger().info('I heard: "%s"' % msg.data)
def main(args=None):
# Initialize the rclpy library
rclpy.init(args=args)
# Create a subscriber
minimal_subscriber = MinimalSubscriber()
# Spin the node so the callback function is called.
# Pull messages from any topics this node is subscribed to.
rclpy.spin(minimal_subscriber)
# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
minimal_subscriber.destroy_node()
# Shutdown the ROS client library for Python
rclpy.shutdown()
if __name__ == '__main__':
main()
Save the file, and close it.
Modify Setup.py
Go to the dev_ws/src/py_pubsub folder.
cd ~/dev_ws/src/py_pubsub
Open setup.py.
gedit setup.py
Add the following line within the console_scripts brackets of the entry_points field:
entry_points={
'console_scripts': [
'my_publisher = py_pubsub.my_publisher_node:main',
'my_subscriber = py_pubsub.my_subscriber_node:main',
],
},
Save and close the file.
Build the Package
Return to the root of your workspace:
cd ~/dev_ws/
We need to double check that all the dependencies needed (rclpy and std_msgs) are already installed.
rosdep install -i --from-path src --rosdistro foxy -y
Build all packages in the workspace.
colcon build
Alternatively, if you only want to build py_pubsub and no other package in the workspace, you can type:
colcon build --packages-select py_pubsub
The build was successful.
Run the Nodes
To run the nodes, open a new terminal window.
Make sure you are in the root of your workspace:
cd ~/dev_ws/
Run the publisher node. If you recall, its name is my_publisher.
ros2 run py_pubsub my_publisher
You will see a message published every 500 milliseconds.
Open a new terminal, and run the subscriber node.
ros2 run py_pubsub my_subscriber
Let’s see what topics are currently active. Open a new terminal, and type:
ros2 topic list -t
Let’s listen to the addison topic.
ros2 topic echo /addison
Press CTRL + C
Now let’s see the node graph.
rqt_graph
You can see the relationship between the nodes.
minimal_publisher publishes data to the /addison topic. minimal_subscriber subscribes to data published on that topic.
Press CTRL + C in all terminals to shutdown the programs.
Create a Launch File
Now let’s create a ROS 2 launch file. This launch file will enable us to launch the publisher and subscriber nodes simultaneously with a single command.
Go to the following directory:
cd ~/dev_ws/src/py_pubsub/
Type the following command to create a new folder:
mkdir launch
Move inside that folder.
cd launch
Open up a new launch file.
gedit py_pubsub_launch.py
Write the following code inside the launch file.
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='py_pubsub',
namespace='ns1',
executable='my_publisher',
name='my_publisher_node'
),
Node(
package='py_pubsub',
namespace='ns1',
executable='my_subscriber',
name='my_subscriber_node'
)
])
Click Save and close the file to return to the terminal.
Launch the Launch File
To launch the launch file, open a new terminal window, and move to the launch folder.
cd ~/dev_ws/src/py_pubsub/launch
Type:
ros2 launch py_pubsub_launch.py
Here was my output:
Click Save and close the file to return to the terminal.
Zip the Package for Distribution
If you ever want to zip the package and send it to someone, open a new terminal window.
Move to the directory containing your package.
cd ~/dev_ws/src
zip -r py_pubsub.zip py_pubsub
The syntax is:
zip -r <filename.zip> <foldername>
Final Words
That’s it for creating a basic Python Subscriber and Publisher in ROS 2.
You should also know that you can have a node that both subscribes and publishes. I call this kind of node a publishing subscriber node. I show you how to create this kind of node in this post.
Keep building!