Publisher/Subscriber
In this tutorial, you will learn ROS Nodes (as publisher and subscriber), and create your first two nodes which communicate with each other.

How does publisher/subscriber work?

Nodes are the simplest executable files of a ROS package. They are either written in Python or C++.
In the ROS framework, there are various ways that nodes communicate with each other such as via topic, request/response or parameter. All have advantages and disadvantages but we will focus on topics in this tutorial.
Creating ROS nodes
A ROS node can publish a topic, subscribe to a topic or can to both with several topics. We just need to define it in the code.
Simple Python script
This is a simple Python script.
import a-fancy-library
class myFancyClass():
# This is what automatically runs when you create an object from this class
def __init__(self):
print("initialized")
# This you can call anywhere after you create the object
def another_method(self):
print('Hi from my method')
def main():
print("Do something nice here.")
my_object = MyFancyClass() # Output: initialized
my_object.another_method() # Output: Hi from my method
if __name__ == '__main__':
main()
So now we would like to add ROS elements to this simple code structure to make it a publisher and/or subscriber.
Create a publisher
Create a Python script in the package:
touch ~/ros2_ws/src/my_package/my_package/my_publisher.py
Simple publisher
This is a simple publisher has five main things on top of this simple Python script:
- Proper ros-python imports:
# Those two are a-must import rclpy from rclpy.node import Node # Depending on what topics you want to publish/subscribe, more libraries are needed from std_msgs.msg import String - A class from the ROS Node parent class:
class myPublisherNode(Node): def __init__(self) -> None: super().__init__("my_publisher") # This is what your node name would be self.pub = self.create_publisher(String, 'my_topic', 10) # This is what you's publish self.create_timer(1.0, self.timer_callback) # How often to publish - this publishes every second - A method to do something periodically (Psst: we have already connected this method to a timer in the init function in the last line):
def timer_callback(self): # or you can rename the method - it doesn't need to be called "timer" msg = String() # A msg type msg.data = 'Hello World' # Fill all the fields of the msg with meaningful info self.pub.publish(msg) # Publish it self.get_logger().info('Publishing: "%s"' % msg.data) # Optionally print things out in the terminal - A main function to gather all the tasks to be done in this publisher:
def main(args=None): rclpy.init(args=args) node = myPublisherNode() rclpy.spin(node) rclpy.shutdown() - And finally boilerplate script execution check or main guard, which is a fancy technical term referring a special conditional block for controlling code execution based on how a Python file is invoked. If you run this file as a regular Python script (using the file name like
python my_publisher.py), the code here would run. However, if you import this asfrom my_publisher import myPublisherNode, it doesn’t run this conditional block. It is very common thing in Python and particularly useful in ROS if you want to test your node without actually calling in your overall system.if __name__ == '__main__': main()
That’s it! Let’s gather everything in one Python script. You can just copy-paste the code below.
ros2_ws/src/my_package/my_publisher.py
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class myPublisherNode(Node):
def __init__(self) -> None:
super().__init__("my_publisher")
self.pub = self.create_publisher(String, 'my_topic', 10)
self.create_timer(1.0, self.timer_callback)
def timer_callback(self):
msg = String()
msg.data = 'Hello World'
self.pub.publish(msg)
self.get_logger().info('Publishing: "%s"' % msg.data)
def main(args=None):
rclpy.init(args=args)
node = myPublisherNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
Updating the package with the new publisher
At the moment, it works as a regular Python script but not as a ROS node. There is no autocomplete and ros2 run does not work.
We need to add an entry point in setup.py. This will tell the ROS what to run as an executable node.
entry_points={
'console_scripts': [
'my_publisher = my_package.my_publisher:main'
],
},
and then compile: colcon build and source source install/setup.bash
Note that 1) File name of the node, 2) Node name in the code, and 3) Executable name in the setup.py are not necessarily the same. Nonetheless, it is easier to follow if we keep all the same for now.
Create a subscriber
We have a node publishing the string "Hello”` at the moment. To make it more meaningful, we can create another node that listens to this string. We call these types of nodes Subscribers. Let’s copy-paste the code piece below and discuss how it works.
Simple subscriber
1.Create the Python script: touch ~/ros2_ws/src/my_package/my_package/my_subscriber.py
2.Copy-paste the code below:
ros2_ws/src/my_package/my_subscriber.py (Completed)
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class mySubscriberNode(Node):
def __init__(self) -> None:
super().__init__("my_subscriber")
self.sub = self.create_subscription(String, 'my_topic', self.listener_callback, 10)
print("Created")
def listener_callback(self, msg):
self.get_logger().info('I heard: "%s"' % msg.data)
def main(args=None):
rclpy.init(args=args)
node = mySubscriberNode()
rclpy.spin(node)
rclpy.shutdown()
if __name__ == '__main__':
main()
We are done with the content of the subscriber. Make sure that my_topic is the same as your publisher!
Updating the package with the new subscriber
Again, the ROS package my_package has no idea that it has a new executable! ros2 run does not work yet. We need to let them know. Add an entry point in setup.py.
entry_points={
'console_scripts': [
'my_publisher = my_package.my_publisher:main',
'my_subscriber = my_package.my_subscriber:main' # This line is new - you can just copy-paste this
],
},
Run nodes
Now we have a publisher and a subscriber nodes. It is time to run them and observe that they communicate successfully.
- Open your favorite terminal: Ctrl+Alt+T
- Make sure that you are in the right directory:
cd ~/ros2_ws - Compile the workspace:
colcon build --symlink-install - Source the workspace:
source install/setup.bash - Run publisher:
ros2 run my_package my_publisher - Open a new terminal:Ctrl+Alt+T
- Run subscriber:
ros2 run my_package my_subscriber

Understanting topics/messages
A message is data and a topic is the channel where nodes are subscribed to read messages or where the nodes publish those messages.
So far, we only focused on the String type of message and wrote a simple text. There are other message types which are used commonly in robotics projects such as Pose, Position, Vector3, Twist, etc. Each message type in ROS is defined in the respective library. For instance, the String is in the standard messages library as you see in the beginning of the previous codes: from std_msgs.msg import String.
The message types can be quite generic like in geometry_msgs and std_msgs, or intended to be used in specific cases like in sensor_msgs and nav_msgs. You can also create your own message type, which will be discussed later.
Turtlesim tutorial
In this part of the tutorial, we will learn about a very common topic /cmd_vel which often controls the velocity of a robot.
As we mentioned before, ROS has a sweet obsession with turtles. The logos of each ROS distribution has a turtle, the mobile robots which you will work on lab assignments are called Turtlebots and the tutorial that we will do now is on turtlesim.
Run the turtlesim node: ros2 run turtlesim turtlesim_node. You will see a simulated turtle.

The turtlesim is a package that comes with ROS generic installation. You do not see a package named turtlesim under your ~/ros2_ws/src directory but the code above works just fine!
If you are curious, all the default packages are in /opt/ros/foxy/share. You can use this command: ros2 pkg prefix turtlesim
In the ROS world, we can say that this turtle represents a mobile robot. We can control it as if it was a robot then. Luckily, the turtlesim package has an implemented publisher node that publishes \cmd_vel topic to the turtlesim_node.
Run the teleoperation node: ros2 run turtlesim turtle_teleop_key. You will be able to control the turtle with the arrow keys on your keyboard.
Make sure that the terminal which the turtle_teleop_key node is running is selected, NOT THE SIMULATION WINDOW. Otherwise, you cannot control the turtle.
Visualize nodes and topics with rqt
At the moment, a lot is going on in the background.
- There are 2 nodes running:
ros2 node list/teleop_turtle /turtlesim - A few topics are available:
ros2 topic list- one of which is /cmd_vel./parameter_events /rosout /turtle1/cmd_vel /turtle1/color_sensor /turtle1/pose - The message type of the /turtle1/cmd_vel is Twist:
ros2 topic info /turtle1/cmd_vel. There are 1 publisher and 1 subscriber node of this topic.Type: geometry_msgs/msg/Twist Publisher count: 1 Subscription count: 1
And there is much more that you can observe with ros2 topic/param/service/node list/info but these are enough for this tutorial. You will learn different communication patterns of ROS later. These will make more sense there.
One last cool thing is that you can see all these visually instead.
Type: ros2 run rqt_graph rqt_graph

You can see what other executable nodes are available for turtlesim package by using the following command: ros2 pkg executables turtlesim
Turtlesim and /cmd_vel exercise
This part is voluntary.
Can you write a publisher that makes the turtle draw a circle?
Turtlesim subscriber exercise
Can you add a subscriber to the pose topic to your cmd_vel publisher that prints out the x and y position as well as the current orientation of the turtle?
Extra Challenge: Can you use the pose subscriber to make the turtle draw an 8?