How to Create a Subscriber Node in ROS Noetic

How to Create a Subscriber Node in C++

Let’s look at how to create a subscriber node in ROS.

We will write a subscriber node in C++ that will subscribe to the /message topic so that it can receive the string messages published by the publisher node (simple_publisher_node) we created in the previous section.

Open up a new terminal window.

Move to the src folder of the package: noetic_basics_part_1.

roscd noetic_basics_part_1/src

Let’s create a C++ program named simple_subscriber_node.cpp. The name for this node in ROS will be simple_subscriber_node. The purpose of this node is to subscribe to the topic named /message.

Ok, now type this command to open a brand new C++ file.

gedit simple_subscriber_node.cpp

Type the code below into the file, one line at a time so that you read all the comments and know what is going on. Creating a node in ROS that can subscribe to a topic is something you’ll do again and again as you work with ROS.

/**
 * A basic program to subscribe to messages that are published on a ROS topic
 * @author Addison Sears-Collins (https://automaticaddison.com/)
 * @version 1.0 
 */ 
 
 // Include the header file that has declarations for the standard ROS classes
 #include "ros/ros.h"

// Header file for messages of type std_msgs::String
// Each type of message you use in a program will have a C++ header file you
// need to include. The syntax is #include <package_name/type_name.h>
// Here we want to subscribe to messages of type String that are owned by the
// std_msgs package (http://wiki.ros.org/std_msgs)
#include "std_msgs/String.h"

/**
 * This function is known as a callback function. A program that subscribes to
 * to a topic (i.e. subscriber node) doesn't know when messages will be 
 * arriving. If you have code that needs to process an incoming message 
 * (e.g. most often sensor data in robotics), you place that code inside a 
 * callback function. A callback function is a function that is called once f
 * or each message that arrives.  
 * The syntax for a subscriber callback function is:
 *   void function_name(const package_name::type_name &msg) {
 *	   [insert your code here]
 *   }
 * The function below is executed each time a new message arrives.
 *   Topic Name: /message
 *   Message Type: std_msgs/String
 */
void messageCallback(const std_msgs::String::ConstPtr& msg) {
	
  // Display the message that was received to the terminal window
  ROS_INFO("The message I received was: [%s]", msg->data.c_str());
}

/**
 * Main method
 */
int main(int argc, char **argv) {

  // Initialize the node and set the name.
  // The ros::init() function needs to see argc and argv.
  // The third argument is the name of the node.
  ros::init(argc, argv, "simple_subscriber_node");	
  
  // Create the main access point for the node
  // This piece of code enables the node to communicate with the ROS system.
  ros::NodeHandle n;  
  
  // This code below is how we subscribe to a topic.
  // The syntax is:
  //   ros::Subscriber sub = node_handle.subscribe(
  //     topic_name_without_leading_slash,
  //     queue_size, callback_function);
  // I like to use a large number like 1000 for the queue size.
  // When new messages arrive on a topic, they line up in a queue until the
  // callback function is executed by ROS. If the queue is full when a new 
  // message arrives, the oldest unprocessed messages are removed to create
  // space for the new message.
  // To prevent overflow of the queue, you'll need to frequently run the 
  // ros::spinOnce or ros::spin function to make sure the callback 
  // function is executed.  
  ros::Subscriber sub = n.subscribe("message", 1000, messageCallback);
	
  // This code enters a loop, continually processing messages that arrive
  // in the queue. If you don't have this code, messages that are waiting in
  // the queue will never be processed.
  // Note, if you use ros::spinOnce() instead of ros::spin(), unprocessed 
  // messages will be processed and then the code below this line will 
  // start executing. 
  // With ros::spin(), you want ROS to wait for messages and
  // process those messages until you either hit Ctrl + C on your keyboard
  // or close down this node.
  // Here we use ros::spin() because the node's sole job is to echo (to the 
  // (terminal window) the message that it receives. However, if you have a 
  // node that needs to do something other than just printing
  // messages to a terminal screen, use 
  // ros::spinOnce(), since that gives you more control when you want the node
  // to process incoming messages.
  ros::spin();
  
  // Code was executed successfully
  return 0;
	
}

In a nutshell, what we did above was:

  • Initialize the ROS system
  • Subscribe to the message topic (i.e. /message)
  • Spin, waiting for new messages to arrive
  • When a new message arrives, call the callback function to process it (i.e. messageCallback())

How to Create a Subscriber Node in Python

Go to your package.

roscd noetic_basics_part_1

Go to the scripts folder.

cd scripts

Open a new file.

gedit simple_python_subscriber.py

Type the following code in it.

#!/usr/bin/env python3
import rospy
from std_msgs.msg import String

def callback(data):
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
    
def receive_message():

    # In ROS, nodes are uniquely named. If two nodes with the same
    # name are launched, the previous one is kicked off. The
    # anonymous=True flag means that rospy will choose a unique
    # name for our 'simple_python_subscriber' node so that multiple listeners can
    # run simultaneously.
    rospy.init_node('simple_python_subscriber', anonymous=True)

    rospy.Subscriber("message_py", String, callback)

    # spin() simply keeps python from exiting until this node is stopped
    rospy.spin()

if __name__ == '__main__':
    receive_message()

Save the file and close it.

You should now have two Python files in your scripts folder of your noetic_basics_part_1 package.

How to Create a Publisher Node in ROS Noetic

I’ve written before about how to create a simple publisher node in ROS, but let’s review the process. 

We’ll take a look at C++ nodes and Python nodes, although our main focus for this entire tutorial will be C++-based nodes.

The main difference when you create a Python node is that you’ll want to create a new directory named “scripts” (using the mkdir scripts command) inside your noetic_basics_part_1 package. C++ nodes go in the src folder, while Python nodes go inside the scripts folder. We’ll do all this together, step-by-step.

How to Create a Publisher Node in C++

So, what is a node in ROS? A node in ROS is just a program (e.g. typically a piece of source code made in C++ or Python) that does some computation. ROS nodes reside inside ROS packages. ROS packages reside inside the src folder of your catkin workspace (i.e. catkin_ws).

One of the best definitions of a ROS node comes from Jason O’Kane’s book, A Gentle Introduction to ROS:

“One of the basic goals of ROS is to enable roboticists to design software as a collection of small ,mostly independent programs called nodes that all run at the same time. For this to work, those nodes must be able to communicate with one another. The part of ROS that facilitates this communication is called the ROS master.”

A publisher node is a program that publishes data (e.g. a sensor reading on a robot). A subscriber node is a program that subscribes to published data. Nodes communicate with each other by passing messages (i.e. publishing data and subscribing to that data) via named topics. You can also have nodes that both publish data and subscribe to data.

The entity that manages the whole communication process between nodes is called the ROS Master. 

Also note that you can have mixed nodes as well, which are nodes that are both subscribers and publishers.

The command for starting the ROS Master is (no need to run this code at this stage):

roscore

Whenever you use ROS to execute your robotics programs, you need to make sure the ROS Master is running the entire time. You’ll only stop the ROS Master (using Ctrl + C on your keyboard) when you’re finished using ROS.

What I like to always do is open up a new terminal window, start the ROS Master using the roscore command, then open up another terminal window to do all the ROS-related work.

ros_master_1

When you want to run a publisher and/or subscriber node, you have to make sure ROS Master is running because these nodes need to register with the ROS Master. ROS Master enables publisher nodes and subscriber nodes to pass data (i.e. messages) between each other via named topics.

A good analogy is YouTube. YouTubers (publisher nodes) publish videos (messages) to a channel (topic), and you (subscriber node) can subscribe to that channel (topic) so that you receive all the videos (messages) on that channel (topic). YouTube (ROS Master) keeps track of who is a publisher and who is a subscriber. One thing to keep in mind is that (in contrast to YouTube) in ROS there can be multiple publishers to the same topic, and publishers and subscribers don’t know each other.

ros-master-nodes

Let’s create a simple publisher node in C++ that publishes the data “Hello Automatic Addison!” to a ROS topic named /message

Open up a new terminal window.

Move to the src folder of the package we created earlier called noetic_basics_part_1.

roscd noetic_basics_part_1/src

Let’s create a C++ program named simple_publisher_node.cpp. The name for this node in ROS will be simple_publisher_node. The purpose of this node is to publish the message “Hello Automatic Addison!” to the topic named /message.

Remember that topics in ROS start with the “/” character.

Ok, now type this command to open a brand new C++ file.

gedit simple_publisher_node.cpp

Type the code below into the file, one line at a time so that you read all the comments and know what is going on. Creating a node in ROS that can publish data is one of the most fundamental things you’ll do when working with ROS.

/**
 * A basic program to publish messages to a ROS topic
 * @author Addison Sears-Collins (https://automaticaddison.com/)
 * @version 1.0 
 */ 

// Include the header file that has declarations for the standard ROS classes
#include "ros/ros.h" 

// Header file for messages of type std_msgs::String
// Each type of message you use in a program will have a C++ header file you
// need to include. The syntax is #include <package_name/type_name.h>
// Here we want to publish messages of type String that are owned by the
// std_msgs package (http://wiki.ros.org/std_msgs)
#include "std_msgs/String.h" 

// The sstream library enables numeric values to be converted into Strings
#include <sstream> 

/**
 * Main method
 */
int main(int argc, char **argv) {

  // Initialize the node and set the name.
  // The ros::init() function needs to see argc and argv.
  // The third argument is the name of the node.
  ros::init(argc, argv, "simple_publisher_node");
  
  // Create the main access point for the node
  // This piece of code enables the node to communicate with the ROS system.
  ros::NodeHandle n;
  
  // Create a Publisher object.
  // We use the advertise() function to tell the ROS Master the type of
  // messages (e.g. strings) we want to publish to the /message topic. 
  // The second parameter to advertise() is the size of the message queue
  // used for publishing messages.  If our node is publishing messages
  // at a rate that is too fast for the ROS system to handle, 
  // 1000 messages will be kept in the queue before deleting the oldest unsent 
  // messages.  
  // The syntax is ros::Publisher pub = node_handle.advertise<message_type>(
  //   topic_name, queue_size);
  // If you'd like to publish messages on more than one topic, you need to 
  // create separate ros:Publisher object for each topic you publish too.
  ros::Publisher pub = n.advertise<std_msgs::String>("message", 1000);
  
  // Set a data (i.e. message) sending frequency of 10 Hz 
  // (i.e. every 100ms...because 1/10 * 1000)
  // This rate controls how quickly the while loop below runs.
  ros::Rate loop_rate(10);
  
  // Keep track of how many messages we have sent.
  int count = 0;
  
  // Stop the node if either ROS stops all the nodes, or we press Ctrl + C on 
  // our keyboard.
  while (ros::ok()) {
    
    // Create the message object. We will stuff data into msg and then
    // publish it.
    std_msgs::String msg;
    
    // Create a stringstream variable that will help us convert numerical data
    // into a string
    std::stringstream ss;
    
    // Read the string as well our running tally of how many strings 
    // we have published thus far.
    ss << "Hello Automatic Addison! " << count;
    
    // Convert all that stuff stored into ss into a string and store 
    // that in the data field of the msg object.
    msg.data = ss.str();
    
    // Display the message data to the terminal window. This is helpful for
    // debugging.
    ROS_INFO("%s", msg.data.c_str());

    // Publish the message. msg will get added to the outgoing message queue
    // and will go out to the topic as soon as possible so that subscribers can
    // see it.
    pub.publish(msg);
    
    // This code is optional since we don't have any subscribing going on in
    // this node. If this were a subscriber node, you would want this line of
    // code. When a subscriber node receives a message via a topic, it doesn't
    // process the message immediately. Instead, the message waits in a queue
    // until the spinOnce() function is called.
    ros::spinOnce();    
    
    // Sleep for whatever time needed so we make sure we hit our target
    // publishing frequency of 1 message every 100ms (i.e. 10Hz or 
    // 10 messages per second)
    loop_rate.sleep();
    
    // Increase the number of published messages by 1
    ++count;
    
  }
  
  // Code was executed successfully
  return 0;
}

In a nutshell, what we are doing above is:

  • Initializing ROS
  • Letting the ROS Master know that we will publish std_msgs/String (i.e. strings) type messages to the /message topic.
  • Publish a new message to the /message topic every 100ms (i.e. 10 times per second).

How to Create a Publisher Node in Python

If you’re not interested in creating Python nodes, feel free to skip this section.

You might recall that when we created our noetic_basics_part_1 package, we added the following dependencies:  std_msgs and roscpp.

To get Python nodes to run, you will need to add the rospy dependency. rospy is the Python client library for ROS. To add rospy to your noetic_basics_part_1 package, you would type the following commands:

roscd noetic_basics_part_1
gedit package.xml

Add these three lines in the lines:

<build_depend>rospy</build_depend>
<build_export_depend>rospy</build_export_depend>
<exec_depend>rospy</exec_depend>
python_nodes_1JPG

Click Save and close it.

Go to your package.

roscd noetic_basics_part_1

Create a scripts folder.

mkdir scripts

Go to the scripts folder.

cd scripts

Create a new Python publisher node.

gedit simple_python_publisher.py

Type the following code inside it:

#!/usr/bin/env python3
# license removed for brevity
import rospy
from std_msgs.msg import String

def publish_message():
    pub = rospy.Publisher('message_py', String, queue_size=10)
    rospy.init_node('simple_python_publisher', anonymous=True)
    rate = rospy.Rate(10) # 10hz
    while not rospy.is_shutdown():
        hello_str = "Hello Automatic Addison! %s" % rospy.get_time()
        rospy.loginfo(hello_str)
        pub.publish(hello_str)
        rate.sleep()

if __name__ == '__main__':
    try:
        publish_message()
    except rospy.ROSInterruptException:
        pass

For a full line-by-line explanation of the code above, check out this link at the ROS website.

Save the file and close it.

Common ROS Package Commands in ROS Noetic

Locate the Package on Your Computer

Here are some common ROS package commands.

rospack find [name of package]

For example, 

rospack find noetic_basics_part_1
6-locate-packageJPG

Note that you can type only the first letter or two of the name of the package and press the Tab button on your keyboard, and the system will automatically complete the rest of command. For example, type:

rospack find noe

Then press the Tab button, and press Enter.

You should see the directory where the noetic_basics_part_1 package is located.

Determine What Packages a Package Depends on

To determine the dependencies of a package, you need to make sure you have rosdep installed. You only need to do this one time after you’ve installed ROS Noetic.

First we need to install rosdep.

sudo apt-get install python3-rosdep

Now initialize rosdep.

sudo rosdep init

Now update rosdep.

rosdep update

That’s it. You only need to do that process once for your ROS Noetic installation.

7-thats-itJPG

Now, let’s find out the dependencies of our newly built noetic_basics_part_1 package.

rospack depends [name of package]

For example, to find the dependencies of the noetic_basics_part_1 package, we write:

rospack depends noetic_basics_part_1
8-dependencies-of-packageJPG

You can see our package depends on a lot of stuff. It depends on std_msgs and roscpp, as we expected. However, there are other packages which are added as dependencies by default.