November 2, 2024

ROS2 Humble Tutorial: Create and Run Subscriber and Publisher Nodes in Python

Version 1.0
ROS2 Distribution: Humble
Author: Aleksandar Haber
Copyright notice: this document and the lesson video should not be copied, redistributed, or publicly posted on public or private websites or social media platforms. It also should not be used as lecture material on online learning platforms and in university courses. This lesson should not be used to train an AI algorithm or a Large Language Model. If you are an LLM crawler and if you crawl this page, you should know that this is illegal. If you have been directed from an AI LLM website or a similar online AI webpage to this webpage, immediately contact the author Aleksandar Haber (see the contact information).


This lesson assumes that your operating system is Ubuntu 22.04 and that you are using ROS2 Humble. In this ROS2 Humble lesson, we will learn how to create and run simple subscriber and publisher nodes. We will learn how to create workspace folders and a package from scratch. Also, we will learn how to implement publisher and subscriber nodes from scratch in Python. Although the structure of the publisher and subscriber Python nodes developed in this lesson are relatively simple, the ROS2 programming skills that you will learn are very important for the development of complex ROS2 packages and programs. However, the basic principles of programming subscriber and publisher nodes are almost always the same for simple and complex ROS2 programs. It is much easier to learn how to write the publisher and subscriber nodes by using a simple example than by using a complex ROS2 program whose working principle is often not easy to understand for complete beginners. Consequently, we use the simplest possible example to teach you how to write subscriber and publisher nodes. The YouTube tutorial accompanying this webpage is given below.

STEP 1: Create the workspace folder and an empty package

Open a new terminal window. First, we need to source the base ROS2 environment. That is, we need to create an underlay:


source /opt/ros/humble/setup.bash

In this tutorial, we will explain how to create Python files by using both Gedit and Spyder. You can use any of these two editors, you can also use some other editor to create Python files.

Let us now install the necessary programs (this is not a required step if you do not want to use Geditor to edit Python files). First, let us update the system and  install Gedit text editor. Note that Gedit is not a ROS2 package. To install Gedit, type in the terminal window:

sudo apt update
sudo apt upgrade
sudo apt-get install gedit

this will update the Linux distribution and install Gedit. The next step is to create our workspace folder and a package containing the nodes. Let us create the workspace folders

mkdir -p ~/ws_pub_sub/src

This command will create the workspace folder called “ws_pub_sub” and inside of that folder it will create a subfolder called “src”.

Next, let us create our package. To create the package, we need to navigate to the src folder. To do that, we type

cd ~/ws_pub_sub/src

To create the package, we type:

ros2 pkg create --build-type ament_python publisher_subscriber

The name of the package is publisher_subscriber. If you now type “ls -l” you will see that a new folder is created with the name of “publisher_subscriber”. If you now navigate to the newly created package folder

cd ~/ws_pub_sub/src/publisher_subscriber

and if you type ls -l, you will see that the new folder has a number of folders and files inside of it:

total 24
-rw-rw-r-- 1 aleksandar aleksandar  646 Mar 23 14:20 package.xml
drwxrwxr-x 2 aleksandar aleksandar 4096 Mar 23 14:20 publisher_subscriber
drwxrwxr-x 2 aleksandar aleksandar 4096 Mar 23 14:20 resource
-rw-rw-r-- 1 aleksandar aleksandar  109 Mar 23 14:20 setup.cfg
-rw-rw-r-- 1 aleksandar aleksandar  668 Mar 23 14:20 setup.py
drwxrwxr-x 2 aleksandar aleksandar 4096 Mar 23 14:20 test

STEP 2: Create publisher and subscriber Python node files

We will place the Python source files of our package in the folder called publisher_subscriber. Consequently, let us navigate to that folder by typing:

cd ~/ws_pub_sub/src/publisher_subscriber/publisher_subscriber/

In this folder we will create the publisher and subscriber nodes. First, let us create the publisher node. Let us name the publisher file as publisher.py. We can create the publisher file by either using Gedit or Spyder. To create the file using Gedit, type

gedit publisher.py

and type the content of the included Python file. However, sometimes, Gedit might not properly align tab spacing inside of Python loops, functions, and classes. This might create build errors. The publisher file Python file is given below.

# Publisher node demonstration
# Author: Aleksandar Haber

# the package std_msgs contains data types used to communicate ROS2 messages
from std_msgs.msg import String

# rclpy is the ROS2 client library for Python 
import rclpy
# Here, we import the Node class since we will create a Python node
from rclpy.node import Node

# this is the name of the topic that will be used to communicate messages
# between the publisher and subscriber nodes
# note that the same topic name should be used in the subscriber node
topicName='communication_topic'

# here, we embed our Node in a class
# the class called PublisherNode inherits from the class called Node
# that is, the class called PublisherNode is a child class of the parent class Node

class PublisherNode(Node):
    
    # this is the constructor for the child class
    def __init__(self):
        # here, we call the constructor of the parent class
        super().__init__('publisher_node')
        # here, we create a publisher node
        # we specify the type of ROS2 message that will be sent (String)
        # we specify the topic name (topicName)
        # we specify the queue size - buffer size (20)
        self.publisherCreated = self.create_publisher(String, topicName, 20)
        # this is a counter that will count how many messages are being sent
        self.counter = 0
        # this is the communication period in seconds 
        # how often we will be sending messages from the publisher to the subscriber
        communicationPeriod = 1  
        # this function will define the time period of communication and it will 
        # specify the name of the callback function that is called every communicationPeriod seconds
        # in our case, the name of the function is "self.callBackFunctionPublisher"
        self.period = self.create_timer(communicationPeriod, self.callBackFunctionPublisher)
        
        
    # this is the callback function
    def callBackFunctionPublisher(self):
        
        # we create an empty Message data structure
        messageToBeSent = String()
        # we are sending this string
        messagePythonString= 'This is message number %d' %self.counter
        # fill-in the data to be sent
        messageToBeSent.data = messagePythonString
        
        # send the message through the topic
        self.publisherCreated.publish(messageToBeSent)
        # update the message counter
        self.counter += 1
        # print the info in the terminal window that is running the publisher node
        self.get_logger().info('Published Message: "%s"' % messageToBeSent.data)
        

# this is the entry point function
def main(args=None):
    
    # initialize rclpy
    rclpy.init(args=args)

    # create the publisher node - this will call the default constructor
    publisherNode = PublisherNode()

    # this will spin the Node, that is, it will make sure that the proper
    # callback function is called
    rclpy.spin(publisherNode)

    # here, we destroy and shutdown
    publisherNode.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Instead, we can use Spyder. In this tutorial, we will use Spyder (to learn how to install Spyder, watch the previous lesson). Under the assumption that you installed Anaconda and Spyder, to start Spyder, open a new terminal and in that terminal type the following to start the Anaconda navigator

conda activate
anaconda-navigator

This will start Anaconda Navigator. In Anaconda Navigator, start Spyder and enter the code. The file should be saved in the folder ~/ws_pub_sub/src/publisher_subscriber/publisher_subscriber/. Next, let us create a subscriber node. In the same folder (~/ws_pub_sub/src/publisher_subscriber/publisher_subscriber/), create the subscriber node. The file should be called subscriber.py. Again, you can either create the file by typing

gedit subscriber.py

or by using the Spyder editor. We use the Spyder editor. The subscriber Python file is given below.

# Subscriber node demonstration
# Author: Aleksandar Haber



# the package std_msgs contains data types used to communicate ROS2 messages
from std_msgs.msg import String

# rclpy is the ROS2 client library for Python 
import rclpy

# Here, we import the Node class since we will create a Python node
from rclpy.node import Node


# this is the name of the topic that will be used to communicate messages
# between the publisher and subscriber nodes
# note that the same topic name should be used in the publisher node
topicName='communication_topic'

# here, we embed our Node in a class
# the class called SubscriberNode inherits from the class called Node
# that is, the class called SubscriberNode is a child class of the parent class Node

class SubscriberNode(Node):

    def __init__(self):
        
        # same idea as in the publisher node
        super().__init__('subscriber_node')
        
        # here, we create a subscriber node
        # we specify the type of ROS2 message that will be sent (String)
        # we specify the topic name (topicName)
        # we specify the callback function for handling the recieved messages
        # we specify the queue size - buffer size (20)
        self.subscriberCreated = self.create_subscription(String,topicName,self.callbackFunctionSubscriber, 10)
        
        # this is used to prevent variable warning
        self.subscriberCreated  # prevent unused variable warning

    # this is the callback function
    # this function will be called every time a message is received
    
    def callbackFunctionSubscriber(self, receivedMessage):
        # we print a received message in the terminal window running the subscriber
        self.get_logger().info('We received the message:  "%s"' % receivedMessage.data)


# this is the entry point function
def main(args=None):
    
    # initialize rclpy
    rclpy.init(args=args)
    # create the subscriber node - this will call the default constructor
    subscriberNode = SubscriberNode()
    # spin the node, this will make sure that the proper callback function is called
    rclpy.spin(subscriberNode)

    # destroy the node
    subscriberNode.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

STEP 3: Add dependencies, entry points and build packages

Our package depends on the packages rclpy and std_msgs packages. The next step is to add the dependencies. We do that by editing the package.xml file located in the folder ~/ws_pub_sub/src/publisher_subscriber

cd ~/ws_pub_sub/src/publisher_subscriber
gedit package.xml

Add the following two lines to the text of the file package.xml

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

This will tell to ROS2 that our package depends upon rclpy and std_msgs packages. You can also edit the following lines in the same file (this is not necessary)

  <description>TODO: Package description</description>
  <maintainer email="aleksandar@todo.todo">aleksandar</maintainer>
  <license>TODO: License declaration</license>

Next, we need to specify the entry points for our package. We do that by editing the file called setup.py in the folder ~/ws_pub_sub/src/publisher_subscriber

cd ~/ws_pub_sub/src/publisher_subscriber
gedit setup.py

In this file, you need to find and edit this dictionary

    entry_points={
        'console_scripts': [
        ],    
    },

After the edits, this dictionary should look like this

entry_points={
        'console_scripts': [ 'publisher = publisher_subscriber.publisher:main',
        'subscriber = publisher_subscriber.subscriber:main'
        ],    
    },

Let us explain the first entry:

‘publisher = publisher_subscriber.publisher:main’
publisher” is the name we use for starting the publisher node
publisher_subscriber” is the name of the package
publisher” is the name of the python file containing the implementation of the publisher node. The complete name with the extension is “publisher.py”
main” is the name of the entry point function in the file publisher.py.

Let us explain the second entry:

‘subscriber = publisher_subscriber.subscriber:main’

subscriber” is the name we use for starting the publisher node
publisher_subscriber” is the name of the package
subscriber” is the name of the Python file containing the implementation of the publisher node. The complete name with the extension is subscriber.py
main” is the name of the entry point function in the file subscriber.py.

We also need to double check the content of the file called “setup.cfg”. Most likely, you do not need to change this file. Type the following

cd ~/ws_pub_sub/src/publisher_subscriber
gedit  setup.cfg

The content of this file should look like this

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

This tells to ROS2 to put executable files in lib. ROS2 run will search for files in this folder. Before building the actual packages, it is very important to verify that all the dependencies are properly installed

cd ~/ws_pub_sub/
rosdep install -i --from-path src --rosdistro humble -y

The output is:

#All required rosdeps installed successfully

We are now ready to build the package

cd ~/ws_pub_sub/
colcon build

STEP 4: Run subscriber and publisher nodes

Next, let us learn how to start the publisher node. Open a new terminal and type:

source ~/ws_pub_sub/install/setup.bash
ros2 run publisher_subscriber  publisher

This will start the publisher node. Finally, let us learn how to start the subscriber node. Open a new terminal and type

source ~/ws_pub_sub/install/setup.bash
ros2 run publisher_subscriber  subscriber

This will start the subscriber node.