How to Create a Subscriber Node in ROS Noetic

2020-01-07-145135

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.