Learn how to make sure that the Subscriber receives all messages sent by a C++ ROS Publisher to avoid losing any messages from the publisher. This post answers the following question found on the ROS Answers forum: https://answers.ros.org/question/313491/cpp-publishersubscriber-tutorial-loosing-some-messages/. Let’s go!
Related Resources
Step 1: Get your development environment ready
Either of the following will do:
- Use the ROS Development Studio (ROSDS), an online platform for developing for ROS within a PC browser. Easy-peasy. I’m using this option for this post.
- Once you log in, click on the New ROSject button to create a project that will host your code. Then Click on Open ROSject to launch the development/simulation environment.
- To open a “terminal” on ROSDS, pick the
Shell
app from the Tools menu. - You can find the
IDE
app on the Tools menu.
- You have ROS installed on a local PC. Okay, skip to Step 2.
Next step!
Step 2: Reproduce the problem – let’s lose some messages from the publisher!
Create the talker and listener nodes as specified in the tutorial: http://wiki.ros.org/ROS/Tutorials/WritingPublisherSubscriber(c%2B%2B)
1. If you are using ROSDS, start by creating a and opening a ROSject as described above.
2. Open a terminal and create a package:
user:~$ cd catkin_ws/src user:~/catkin_ws/src$ catkin_create_pkg talker_listener roscpp Created file talker_listener/CMakeLists.txt Created file talker_listener/package.xml Created folder talker_listener/include/talker_listener Created folder talker_listener/src Successfully created files in /home/user/catkin_ws/src/talker_listener. Please adjust the values in package.xml. user:~/catkin_ws/src$
3. Create the talker and listener source files
user:~/catkin_ws/src$ cd talker_listener/src user:~/catkin_ws/src/talker_listener/src$ touch talker.cpp listener.cpp
4. Open the talker.cpp
and listener.cpp
files in the IDE and paste in the following code
talker.cpp
#include "ros/ros.h" #include "std_msgs/String.h" #include <sstream> /** * This tutorial demonstrates simple sending of messages over the ROS system. */ int main(int argc, char **argv) { /** * The ros::init() function needs to see argc and argv so that it can perform * any ROS arguments and name remapping that were provided at the command line. * For programmatic remappings you can use a different version of init() which takes * remappings directly, but for most command-line programs, passing argc and argv is * the easiest way to do it. The third argument to init() is the name of the node. * * You must call one of the versions of ros::init() before using any other * part of the ROS system. */ ros::init(argc, argv, "talker"); /** * NodeHandle is the main access point to communications with the ROS system. * The first NodeHandle constructed will fully initialize this node, and the last * NodeHandle destructed will close down the node. */ ros::NodeHandle n; /** * The advertise() function is how you tell ROS that you want to * publish on a given topic name. This invokes a call to the ROS * master node, which keeps a registry of who is publishing and who * is subscribing. After this advertise() call is made, the master * node will notify anyone who is trying to subscribe to this topic name, * and they will in turn negotiate a peer-to-peer connection with this * node. advertise() returns a Publisher object which allows you to * publish messages on that topic through a call to publish(). Once * all copies of the returned Publisher object are destroyed, the topic * will be automatically unadvertised. * * The second parameter to advertise() is the size of the message queue * used for publishing messages. If messages are published more quickly * than we can send them, the number here specifies how many messages to * buffer up before throwing some away. */ ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); ros::Rate loop_rate(10); /** * A count of how many messages we have sent. This is used to create * a unique string for each message. */ int count = 0; while (ros::ok()) { /** * This is a message object. You stuff it with data, and then publish it. */ std_msgs::String msg; std::stringstream ss; ss << "hello world " << count; msg.data = ss.str(); ROS_INFO("%s", msg.data.c_str()); /** * The publish() function is how you send messages. The parameter * is the message object. The type of this object must agree with the type * given as a template parameter to the advertise<>() call, as was done * in the constructor above. */ chatter_pub.publish(msg); ros::spinOnce(); loop_rate.sleep(); ++count; } return 0; }
listener.cpp
#include "ros/ros.h" #include "std_msgs/String.h" /** * This tutorial demonstrates simple receipt of messages over the ROS system. */ void chatterCallback(const std_msgs::String::ConstPtr& msg) { ROS_INFO("I heard: [%s]", msg->data.c_str()); } int main(int argc, char **argv) { /** * The ros::init() function needs to see argc and argv so that it can perform * any ROS arguments and name remapping that were provided at the command line. * For programmatic remappings you can use a different version of init() which takes * remappings directly, but for most command-line programs, passing argc and argv is * the easiest way to do it. The third argument to init() is the name of the node. * * You must call one of the versions of ros::init() before using any other * part of the ROS system. */ ros::init(argc, argv, "listener"); /** * NodeHandle is the main access point to communications with the ROS system. * The first NodeHandle constructed will fully initialize this node, and the last * NodeHandle destructed will close down the node. */ ros::NodeHandle n; /** * The subscribe() call is how you tell ROS that you want to receive messages * on a given topic. This invokes a call to the ROS * master node, which keeps a registry of who is publishing and who * is subscribing. Messages are passed to a callback function, here * called chatterCallback. subscribe() returns a Subscriber object that you * must hold on to until you want to unsubscribe. When all copies of the Subscriber * object go out of scope, this callback will automatically be unsubscribed from * this topic. * * The second parameter to the subscribe() function is the size of the message * queue. If messages are arriving faster than they are being processed, this * is the number of messages that will be buffered up before beginning to throw * away the oldest ones. */ ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); /** * ros::spin() will enter a loop, pumping callbacks. With this version, all * callbacks will be called from within this thread (the main one). ros::spin() * will exit when Ctrl-C is pressed, or the node is shutdown by the master. */ ros::spin(); return 0; }
5. Add the following lines to the build section of the CMakeList.txt
file, so the files can build:
add_executable(talker src/talker.cpp) target_link_libraries(talker ${catkin_LIBRARIES}) add_dependencies(talker talker_listener_generate_messages_cpp) add_executable(listener src/listener.cpp) target_link_libraries(listener ${catkin_LIBRARIES}) add_dependencies(listener talker_listener_generate_messages_cpp)
6. Build the package and source the workspace:
user:~/catkin_ws$ catkin_make user:~/catkin_ws$ source devel/setup.bash
7. Start the listener node first in the current terminal and then start the talker node in another terminal.
Start the listener first. You should not see any message yet.
user:~/catkin_ws$ rosrun talker_listener listener
(PS: If you get an error that master
cannot be reached, run nohup roscore &
in the current terminal to start the ROS master)
Start the talker in a new terminal.
user:~$ cd catkin_ws user:~/catkin_ws$ source devel/setup.bash user:~/catkin_ws$ rosrun talker_listener talker
You would notice that even though the talker publishes the messages starting from zero
, the listener only gets the messages starting from 3
(or even 4
):
# Talker user:~/catkin_ws$ rosrun talker_listener talker [ INFO] [1586183868.773366254]: hello world 0 [ INFO] [1586183868.873393538]: hello world 1 [ INFO] [1586183868.973385317]: hello world 2 [ INFO] [1586183869.073401909]: hello world 3 [ INFO] [1586183869.173393278]: hello world 4 [ INFO] [1586183869.273392714]: hello world 5 #### Listener user:~/catkin_ws$ rosrun talker_listener listener [ INFO] [1586183869.073691834]: I heard: [hello world 3] [ INFO] [1586183869.173605854]: I heard: [hello world 4] [ INFO] [1586183869.273767103]: I heard: [hello world 5]
So the problem is real! Let’s fix it in the next section!
Step 2: Fix the problem: avoid losing messages from the publisher
What’s the point of talking when no one is listening? We need to make the talker wait for the listener before publishing the message by adding the following lines to the talker.cpp
file, before the publish
command:
while(chatter_pub.getNumSubscribers() == 0) loop_rate.sleep();
So your talker.cpp
should now be:
#include "ros/ros.h" #include "std_msgs/String.h" #include <sstream> /** * This tutorial demonstrates simple sending of messages over the ROS system. */ int main(int argc, char **argv) { /** * The ros::init() function needs to see argc and argv so that it can perform * any ROS arguments and name remapping that were provided at the command * line. For programmatic remappings you can use a different version of init() * which takes remappings directly, but for most command-line programs, * passing argc and argv is the easiest way to do it. The third argument to * init() is the name of the node. * * You must call one of the versions of ros::init() before using any other * part of the ROS system. */ ros::init(argc, argv, "talker"); /** * NodeHandle is the main access point to communications with the ROS system. * The first NodeHandle constructed will fully initialize this node, and the * last NodeHandle destructed will close down the node. */ ros::NodeHandle n; /** * The advertise() function is how you tell ROS that you want to * publish on a given topic name. This invokes a call to the ROS * master node, which keeps a registry of who is publishing and who * is subscribing. After this advertise() call is made, the master * node will notify anyone who is trying to subscribe to this topic name, * and they will in turn negotiate a peer-to-peer connection with this * node. advertise() returns a Publisher object which allows you to * publish messages on that topic through a call to publish(). Once * all copies of the returned Publisher object are destroyed, the topic * will be automatically unadvertised. * * The second parameter to advertise() is the size of the message queue * used for publishing messages. If messages are published more quickly * than we can send them, the number here specifies how many messages to * buffer up before throwing some away. */ ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); ros::Rate loop_rate(10); /** * A count of how many messages we have sent. This is used to create * a unique string for each message. */ int count = 0; while (ros::ok()) { /** * This is a message object. You stuff it with data, and then publish it. */ std_msgs::String msg; std::stringstream ss; ss << "hello world " << count; msg.data = ss.str(); ROS_INFO("%s", msg.data.c_str()); /** * The publish() function is how you send messages. The parameter * is the message object. The type of this object must agree with the type * given as a template parameter to the advertise<>() call, as was done * in the constructor above. */ while (chatter_pub.getNumSubscribers() == 0) loop_rate.sleep(); chatter_pub.publish(msg); ros::spinOnce(); loop_rate.sleep(); ++count; } return 0; }
Build the package again, from the first terminal:
user:~/catkin_ws$ catkin_make
Start the listener:
user:~/catkin_ws$ source devel/setup.bash user:~/catkin_ws$ rosrun talker_listener listener
Then start the talker:
user:~/catkin_ws$ source devel/setup.bash user:~/catkin_ws$ rosrun talker_listener talker
Now you should see that the messages are in sync:
# Talker user:~/catkin_ws$ rosrun talker_listener talker [ INFO] [1586183868.773366254]: hello world 0 [ INFO] [1586183868.873393538]: hello world 1 [ INFO] [1586183868.973385317]: hello world 2 #### Listener user:~/catkin_ws$ rosrun talker_listener listener [ INFO] [1586183869.073691834]: I heard: [hello world 0] [ INFO] [1586183869.173605854]: I heard: [hello world 1] [ INFO] [1586183869.273767103]: I heard: [hello world 2]
Yes, we did it! See you next time.
Extra: Video of the post
Here below you have a “sights and sounds” version of this post, just in case you prefer it that way. Enjoy!
Feedback
Did you like this post? Do you have any questions about the explanations? Whatever the case, please leave a comment on the comments section below, so we can interact and learn from each other.
If you want to learn about other ROS or ROS2 topics, please let us know in the comments area and we will do a video or post about it.
Edited by Bayode Aderinola
Thank you!