ROS-Ready

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:

  1. First, navigate to the src folder in your workspace:
bash
cd ~/catkin_ws/src/fhtw
  1. Create the package with the catkin_create_pkg command and set the dependencies to roscpp, rospy, and std_msgs:
bash
catkin_create_pkg cpp_tutorial roscpp rospy std_msgs        # rospy and std_msgs are dependencies of the cpp_tutorial package
  1. Navigate to the catkin_ws again:
bash
cd ~/catkin_ws
  1. Now since we using C++ we need to build the package:
bash
catkin_make
  1. Source the workspace:
bash
source devel/setup.bash
  1. Here you can check if the package was created successfully:
bash
rospack profile
  1. Now navigate to the package:
bash
roscd cpp_tutorial
  1. Create a file for publisher and subscriber folder named src:
bash
touch src/publisher.cpp
bash
touch src/subscriber.cpp

Publisher

  1. Now let's create the publisher. Copy the following code to the publisher.cpp file:
cpp
#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;
}
  1. Since C++ is a compiled language, we need to modify the CMakeLists.txt in the src directory of the cpp_tutorial package to build the code. For more about CMakeLists see: http://wiki.ros.org/catkin/CMakeLists.txt
    Here's how the CMakeLists.txt should look:
cmake
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:

  1. Navigate to the catkin_ws directory:
bash
cd ~/catkin_ws
  1. Build the workspace:
bash
catkin_make
  1. Source the workspace:
bash
source ./devel/setup.bash
  1. Check if the package was loaded successfully:
bash
rospack profile

Run the Publisher

  1. In one terminal, start roscore:
bash
roscore
  1. In another terminal, ([ctrl + b] and [Shift + 5]) run the publisher node:
bash
rosrun cpp_tutorial publisher

Nothing 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.

  1. Open a new terminal ([ctrl + b] and [Shift + 2]) and, type rostopic list and check for a topic named /counter.
bash
rostopic list
output:
/counter
/rosout
/rosout_agg

And there is our topic /counter! And some other topics that are created by default when you start roscore.

  1. To request information about a topic type the following command in a terminal: rostopic info /counter
bash
rostopic info /counter
output:
Type: std_msgs/Int32

Publishers:
 * /counter_publisher (http://192.168.0.3:47971/)

Subscribers: None
  1. 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 /counter and check the output of the topic in realtime. The output will look something like this:
bash
rostopic echo /counter
output:
data:
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.

cpp
#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
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

  1. Navigate to the catkin_ws directory:
bash
cd ~/catkin_ws
  1. Build the workspace:
bash
catkin_make
  1. Source the workspace:
bash
source ./devel/setup.bash
  1. Chekc if the package was loaded successfully:
bash
rospack profile

Run the Subscriber

  1. Start roscore in a terminal again:
bash
roscore
  1. In different terminals, run the following commands:
bash
rosrun cpp_tutorial publisher
bash
rosrun cpp_tutorial subscriber

You should see output similar to this:

bash
[ INFO] [1616669812.343155971]: data: 188
[ INFO] [1616669812.843075654]: data: 189
[ INFO] [1616669813.343027535]: data: 190
[ INFO] [1616669813.843042972]: data: 191

You 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.

On this page