How to Build a Node in ROS Noetic

How to Build a C++ Node

Now that we’ve created our two nodes, we need to build them. Building ROS nodes means getting them in a format that enables ROS to use them.

The first thing we need to do is to edit the CMakeLists.txt file inside the noetic_basics_part_1 package. 

The CMakeLists.txt file is the file inside the noetic_basics_part_1 package that was automatically generated when we created the package. CMakeLists.txt says how to build the code we wrote (i.e. the nodes we just created).

Let’s edit the CMakeLists.txt file for the noetic_basics_part_1 package. Open a new terminal window, and type this command:

roscd noetic_basics_part_1 
gedit CMakeLists.txt

You should see a file that looks like this:

9-file-that-looks-like-thisJPG

Now add these lines to the bottom of the CMakeLists.txt file:

add_executable(simple_publisher_node src/simple_publisher_node.cpp)
target_link_libraries(simple_publisher_node ${catkin_LIBRARIES})

add_executable(simple_subscriber_node src/simple_subscriber_node.cpp)
target_link_libraries(simple_subscriber_node ${catkin_LIBRARIES})

In the code above, we have done the following:

  • Create two executables, simple_publisher_node and simple_subscriber_node, which will by default go into the ~/catkin_ws/devel/lib/noetic_basics_part_1 folder.
  • Make sure the nodes get linked to the catkin libraries (i.e. ROS-related libraries).

Now save and close the CMakeLists.txt file.

Open a new terminal window, and type the following commands to build all the nodes in the noetic_basics_part_1 package:

cd ~/catkin_ws
catkin_make 

That command above will build all nodes inside all the packages in your catkin workspace.

11-build-the-packagesJPG

Now, open a new terminal window and go to the catkin_ws/devel/lib/noetic_basics_part_1/ folder.

cd ~/catkin_ws/devel/lib/noetic_basics_part_1/

Type dir to see the files listed. You will see the publisher and subscriber executables. Feel free to close the terminal window now.

12-there-they-areJPG

How to Build a Python Node

Navigate to the scripts folder inside your package. 

roscd noetic_basics_part_1/scripts

Make the files executable. Type these commands, pressing Enter after each.

chmod +x simple_python_publisher.py
chmod +x simple_python_subscriber.py

Open a new terminal window, and type the following commands to build all the nodes in the noetic_basics_part_1 package:

cd ~/catkin_ws
catkin_make 

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.