3. Publisher & Subscriber C++
Writing your first ROS publisher and subscriber in C++
Publisher Subscriber C++
What will you learn in this unit?
- How to create a publisher
- How to create a subscriber
- What are topic messages and how they work
A publisher sends messages of a fixed type onto a named topic. Any number of subscribers can listen on that topic and run a callback whenever a new message arrives. The publisher and subscribers never talk to each other directly — they only know the topic name. The ROS Master (roscore) is the matchmaker: each node registers what it publishes or subscribes to, and the master tells them how to find each other. After that, message data flows peer-to-peer.
Creating a ROS Package
Let's create a package called cpp_tutorial. Execute the following commands in the terminal:
- First, navigate to the
srcfolder in your workspace:
cd ~/catkin_ws/src/fhtw- Create the package with the
catkin_create_pkgcommand and set the dependencies toroscpp,rospy, andstd_msgs:
catkin_create_pkg cpp_tutorial roscpp rospy std_msgs # rospy and std_msgs are dependencies of the cpp_tutorial package- Navigate to the
catkin_wsagain:
cd ~/catkin_ws- Now since we using C++ we need to build the package:
catkin_make- Source the workspace:
source devel/setup.bash- Here you can check if the package was created successfully:
rospack profile- Now navigate to the package:
roscd cpp_tutorial- Create a file for
publisherandsubscriberfolder namedsrc:
touch src/publisher.cpptouch src/subscriber.cppPublisher
- Now let's create the publisher. Copy the following code to the
publisher.cppfile:
#include <ros/ros.h> // Import the ROS libraries for node handling
#include <std_msgs/Int32.h> // Import the Int32 message from the std_msgs package
int main(int argc, char** argv) {
ros::init(argc, argv, "counter_publisher"); // Initiate a node named 'counter_publisher'
ros::NodeHandle nh; // Manage the node, handle publishing and subscribing
ros::Publisher pub = nh.advertise<std_msgs::Int32>("counter", 1000); // Create a Publisher object to publish on the /counter topic
ros::Rate rate(2); // Set the publish rate to 2 Hz
std_msgs::Int32 count; // Create a variable of type Int32
count.data = 0; // Initialize the 'count' variable
while (ros::ok()) {
pub.publish(count); // Publish the message in the 'count' variable
ros::spinOnce();
rate.sleep(); // Maintain the publish rate at 2 Hz
++count.data; // Increment 'count' variable
}
return 0;
}- Since C++ is a compiled language, we need to modify the
CMakeLists.txtin thesrc directory of the cpp_tutorial packageto build the code. For more about CMakeLists see: http://wiki.ros.org/catkin/CMakeLists.txt
Here's how theCMakeLists.txtshould look:
cmake_minimum_required(VERSION 3.0.2)
project(cpp_tutorial)
# Specify which other CMake packages that need to be found to build the project using the CMake find_package function.
# There is always at least one dependency on catkin.
# If other catkin packages are required to build the code the "REQUIRED COMPONENTS" followed by the ros packages automatically turns them into components (In terms of CMake)
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
)
# catkin_package() is a catkin-provided CMake macro.
# This is required to specify catkin-specific information to the build system which in turn is used to generate pkg-config and CMake files.
# This function must be called before declaring any targets with add_library() or add_executable(). The function has
catkin_package(
CATKIN_DEPENDS roscpp std_msgs
)
# The argument to include_directories should be the *_INCLUDE_DIRS variables generated by your find_package calls and any additional directories that need to be included
include_directories(
${catkin_INCLUDE_DIRS}
)
## Build ##
# To specify an executable target that must be built, we must use the add_executable() CMake function.
add_executable(publisher src/publisher.cpp) # We define an executable named publisher build using src/publisher.cpp
# Use the target_link_libraries() function to specify which libraries an executable target links against. This is done typically after an add_executable() call.
target_link_libraries(publisher ${catkin_LIBRARIES})To run the node first build it and then execute the following commands in different terminals:
Build the Node
To build the node execute the following commands in the terminal:
- Navigate to the
catkin_wsdirectory:
cd ~/catkin_ws- Build the workspace:
catkin_make- Source the workspace:
source ./devel/setup.bash- Check if the package was loaded successfully:
rospack profileRun the Publisher
- In one terminal, start
roscore:
roscore- In another terminal, (
[ctrl + b]and[Shift + 5]) run the publisher node:
rosrun cpp_tutorial publisherNothing happening? Well... that's not actually true! We just created a topic named /counter, and published through it as an integer that increases indefinitely. Let's check this out.
A topic is like a pipe. Nodes use topics to publish information for other nodes so that they can communicate. You can find out, at any time, the number of topics in the system by doing a rostopic list. You can also check for a specific topic.
- Open a new terminal (
[ctrl + b]and[Shift + 2]) and, typerostopic listand check for a topic named/counter.
rostopic list/counter
/rosout
/rosout_aggAnd there is our topic /counter! And some other topics that are created by default when you start roscore.
- To request information about a topic type the following command in a terminal:
rostopic info /counter
rostopic info /counterType: std_msgs/Int32
Publishers:
* /counter_publisher (http://192.168.0.3:47971/)
Subscribers: None- The output indicates the type of the message (std_msgs/Int32), the node (/counter_publisher) that is publishing this information, and if there is a node listening (subscribing) to this topic. Now, type
rostopic echo /counterand check the output of the topic in realtime. The output will look something like this:
rostopic echo /counterdata:
95
---
data:
96
---
data:
97
---
data:
98
---Reminder: To stop any process in the terminal, press [ctrl + c] and to close the terminal press [ctrl + d].
Subscriber
You've learned that a topic is a channel where nodes can either write or read information. You've also seen that you can write into a topic using a publisher. A subscriber is a node that reads information from a topic.
#include <ros/ros.h> // Import the ros libraries for node handling
#include <std_msgs/Int32.h> // Import the Int32 msg from the std_msgs
// Import all the necessary ROS libraries and import the Int32 message from the std_msgs package
void Callback(const std_msgs::Int32::ConstPtr& msg) // Define a function called 'Callback' that receives a argument named 'msg'
{
ROS_INFO_STREAM(*msg); // Print the 'msg' we use '*' to dereference the pointer
}
int main(int argc, char** argv) {
ros::init(argc, argv, "counter_subscriber"); // Initiate a Node called 'counter_subscriber'
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe("counter", 1000, Callback); // Create a Subscriber object that will subscribe to the /counter topic and will
// call the 'Callback' function each time it reads something from the topic
ros::spin(); // Create a loop that will keep the program in execution
return 0;
}Modify the CMakeLists.txt to include the subscriber:
cmake_minimum_required(VERSION 3.0.2)
project(cpp_tutorial)
# Specify which other CMake packages that need to be found to build the project using the CMake find_package function.
# There is always at least one dependency on catkin.
# If other catkin packages are required to build the code the "REQUIRED COMPONENTS" followed by the ros packages automatically turns them into components (In terms of CMake)
find_package(catkin REQUIRED COMPONENTS
roscpp
std_msgs
)
# catkin_package() is a catkin-provided CMake macro.
# This is required to specify catkin-specific information to the build system which in turn is used to generate pkg-config and CMake files.
# This function must be called before declaring any targets with add_library() or add_executable(). The function has
catkin_package(
CATKIN_DEPENDS roscpp std_msgs
)
# The argument to include_directories should be the *_INCLUDE_DIRS variables generated by your find_package calls and any additional directories that need to be included
include_directories(
${catkin_INCLUDE_DIRS}
)
## Build ##
# To specify an executable target that must be built, we must use the add_executable() CMake function.
add_executable(publisher src/publisher.cpp) # We define an executable named publisher build using src/publisher.cpp
add_executable(subscriber src/subscriber.cpp) # We define an executable named subscriber build using src/subscriber.cpp
# Use the target_link_libraries() function to specify which libraries an executable target links against. This is done typically after an add_executable() call.
target_link_libraries(publisher ${catkin_LIBRARIES})
target_link_libraries(subscriber ${catkin_LIBRARIES})Build the Node
- Navigate to the
catkin_wsdirectory:
cd ~/catkin_ws- Build the workspace:
catkin_make- Source the workspace:
source ./devel/setup.bash- Chekc if the package was loaded successfully:
rospack profileRun the Subscriber
- Start
roscorein a terminal again:
roscore- In
different terminals, run the following commands:
rosrun cpp_tutorial publisherrosrun cpp_tutorial subscriberYou should see output similar to this:
[ INFO] [1616669812.343155971]: data: 188
[ INFO] [1616669812.843075654]: data: 189
[ INFO] [1616669813.343027535]: data: 190
[ INFO] [1616669813.843042972]: data: 191You made it 🥳! You created a publisher and a subscriber in C++! Now you can create your own nodes and make them communicate with each other.