Note: This tutorial assumes that you have completed the previous tutorials: installing ROS.
(!) Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Writing a Simple Publisher and Subscriber (C++) (plain cmake)

Description: This tutorial covers how to write a publisher and subscriber node in C++ using plain cmake (i.e., not catkin).

Tutorial Level: BEGINNER

Note: Be sure that you have already installed ROS and remember that you must source the appropriate setup file in every shell you create.

Making a directory in which to work

To help keep yourself organized, make a directory in which to work. You can use any name; we'll just call it pubsub:

mkdir -p pubsub
cd pubsub

Writing the Publisher Node

"Node" is the ROS term for an executable that is connected to the ROS network. Here we'll create a publisher ("talker") node which will continually broadcast a message.

The Code

Create a file named talker.cpp within the pubsub directory and paste the following inside it:

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp':

The Code Explained

Now, let's break the code down.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': ros/ros.h is a convenience include that includes all the headers necessary to use the most common public pieces of the ROS system.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': This includes the std_msgs/String message, which resides in the std_msgs package. This is a header generated automatically from the String.msg file in that package. For more information on message definitions, see the msg page.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': Initialize ROS. This allows ROS to do name remapping through the command line -- not important for now. This is also where we specify the name of our node. Node names must be unique in a running system.

The name used here must be a base name, ie. it cannot have a / in it.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': Create a handle to this process' node. The first NodeHandle created will actually do the initialization of the node, and the last one destructed will cleanup any resources the node was using.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': Tell the master that we are going to be publishing a message of type std_msgs/String on the topic chatter. This lets the master tell any nodes listening on chatter that we are going to publish data on that topic. The second argument is the size of our publishing queue. In this case if we are publishing too quickly it will buffer up a maximum of 1000 messages before beginning to throw away old ones.

NodeHandle::advertise() returns a ros::Publisher object, which serves two purposes: 1) it contains a publish() method that lets you publish messages onto the topic it was created with, and 2) when it goes out of scope, it will automatically unadvertise.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': A ros::Rate object allows you to specify a frequency that you would like to loop at. It will keep track of how long it has been since the last call to Rate::sleep(), and sleep for the correct amount of time.

In this case we tell it we want to run at 10Hz.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': By default roscpp will install a SIGINT handler which provides Ctrl-C handling which will cause ros::ok() to return false if that happens.

ros::ok() will return false if:

  • a SIGINT is received (Ctrl-C)
  • we have been kicked off the network by another node with the same name
  • ros::shutdown() has been called by another part of the application.

  • all ros::NodeHandles have been destroyed

Once ros::ok() returns false, all ROS calls will fail.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': We broadcast a message on ROS using a message-adapted class, generally generated from a msg file. More complicated datatypes are possible, but for now we're going to use the standard String message, which has one member: "data".

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': Now we actually broadcast the message to anyone who is connected.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': ROS_INFO and friends are our replacement for printf/cout. See the rosconsole documentation for more information.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': Calling ros::spinOnce() here is not necessary for this simple program, because we are not receiving any callbacks. However, if you were to add a subscription into this application, and did not have ros::spinOnce() here, your callbacks would never get called. So, add it for good measure.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/talker/talker.cpp': Now we use the ros::Rate object to sleep for the time remaining to let us hit our 10Hz publish rate.

Here's the condensed version of what's going on:

  • Initialize the ROS system
  • Advertise that we are going to be publishing std_msgs/String messages on the chatter topic to the master

  • Loop while publishing messages to chatter 10 times a second

Now we need to write a node to receive the messsages.

Writing the Subscriber Node

The Code

Create a file named listener.cpp within the pubsub directory and paste the following inside it:

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/listener/listener.cpp':

The Code Explained

Now, let's break it down piece by piece, ignoring some pieces that have already been explained above.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/listener/listener.cpp': This is the callback function that will get called when a new message has arrived on the chatter topic. The message is passed in a boost shared_ptr, which means you can store it off if you want, without worrying about it getting deleted underneath you, and without copying the underlying data.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/listener/listener.cpp': Subscribe to the chatter topic with the master. ROS will call the chatterCallback() function whenever a new message arrives. The 2nd argument is the queue size, in case we are not able to process messages fast enough. In this case, if the queue reaches 1000 messages, we will start throwing away old messages as new ones arrive.

NodeHandle::subscribe() returns a ros::Subscriber object, that you must hold on to until you want to unsubscribe. When the Subscriber object is destructed, it will automatically unsubscribe from the chatter topic.

There are versions of the NodeHandle::subscribe() function which allow you to specify a class member function, or even anything callable by a Boost.Function object. The roscpp overview contains more information.

Could not fetch external code from 'https://raw.github.com/ros/ros_tutorials/kinetic-devel/roscpp_tutorials/listener/listener.cpp': ros::spin() enters a loop, calling message callbacks as fast as possible. Don't worry though, if there's nothing for it to do it won't use much CPU. ros::spin() will exit once ros::ok() returns false, which means ros::shutdown() has been called, either by the default Ctrl-C handler, the master telling us to shutdown, or it being called manually.

There are other ways of pumping callbacks, but we won't worry about those here. The roscpp_tutorials package has some demo applications which demonstrate this. The roscpp overview also contains more information.

Again, here's a condensed version of what's going on:

  • Initialize the ROS system
  • Subscribe to the chatter topic

  • Spin, waiting for messages to arrive
  • When a message arrives, the chatterCallback() function is called

Building your nodes

You need to tell cmake how to build (compile and link) your C++ code. Create a file named CMakeLists.txt in the pubsub directory and paste the following inside it:

Could not fetch external code from 'https://raw.githubusercontent.com/gerkey/ros1_external_use/master/ros1_comms/CMakeLists.txt':

Build your nodes in a subdirectory of pubsub named build using the standard cmake sequence:

mkdir -p build
cd build
cmake ..
make

Running the nodes

Running nodes requires you have a ROS core started. Open a new shell, and type:

roscore

If all goes well, you should see an output that looks something like this:

... logging to /u/takayama/.ros/logs/83871c9c-934b-11de-a451-001d927076eb/roslaunch-ads-31831.log
... loading XML file [/wg/stor1a/rosbuild/shared_installation/ros/tools/roslaunch/roscore.xml]
Added core node of type [rosout/rosout] in namespace [/]
started roslaunch server http://ads:54367/

SUMMARY
======

NODES

changing ROS_MASTER_URI to [http://ads:11311/] for starting master locally
starting new master (master configured for auto start)
process[master]: started with pid [31874]
ROS_MASTER_URI=http://ads:11311/
setting /run_id to 83871c9c-934b-11de-a451-001d927076eb
+PARAM [/run_id] by /roslaunch
+PARAM [/roslaunch/uris/ads:54367] by /roslaunch
process[rosout-1]: started with pid [31889]
started core service [/rosout]
+SUB [/time] /rosout http://ads:33744/
+SERVICE [/rosout/get_loggers] /rosout http://ads:33744/
+SERVICE [/rosout/set_logger_level] /rosout http://ads:33744/
+PUB [/rosout_agg] /rosout http://ads:33744/
+SUB [/rosout] /rosout http://ads:33744/

Now everything is set to run talker/listener. Open a new shell, go to your pubsub directory and type:

./build/talker

Now in the original shell type:

./build/listener

Talker should begin outputting text similar to:

[ INFO] I published [Hello there! This is message [0]]
[ INFO] I published [Hello there! This is message [1]]
[ INFO] I published [Hello there! This is message [2]]
...

And listener should begin outputting text similar to:

[ INFO] Received [Hello there! This is message [20]]
[ INFO] Received [Hello there! This is message [21]]
[ INFO] Received [Hello there! This is message [22]]
...

Congratulations! You've just run your first ROS nodes. For more example code, see the roscpp_tutorials package.

Using roslaunch/rosrun

In order for roslaunch to find launch files associated with this node, you'll need to have a package.xml file located next to a launch folder which contains launch files, and you'll need to make sure that the path to that package.xml file can be found in the environment variable ROS_PACKAGE_PATH. The package.xml file just needs a valid name field, i.e. it can be as simple as

<?xml version="1.0"?>
<package>
  <name>my_pkg_name</name>
</package>

In order for rosrun to be able to detect this executable, you'll need to install your executable into a folder structure that look like this:

  • The root folder contains an empty .catkin file
  • The root folder appears in the CMAKE_PREFIX_PATH environment variable
  • The root folder contains either a libexec or share folder which contains a folder named after the package, which then contains your executable

That last one was a little wordy. Here's what the directory will look like:

/path/to/installation_folder
├── .catkin <- empty file
└── libexec <- could be 'share' instead of 'libexec', both are valid
    └── my_pkg_name
        └── my_executable

And /path/to/installation_folder needs to be appear in CMAKE_PREFIX_PATH. You may find it helpful to create your own setup.sh file to autopopulate the ROS_PACKAGE_PATH and CMAKE_PREFIX_PATH variables.

Wiki: ROS/Tutorials/WritingPublisherSubscriber(c++)(plain cmake) (last edited 2020-01-13 02:33:50 by actinium226)