ROS learning - write a simple Publisher and Subscriber

Write a Publisher node

"Node" is a term for executable files in ROS, which is connected to the network of ROS. Here, we will create a Publisher node (talker) to continuously broadcast the message.
First, switch to begin_ The location of the tutorials package.

roscd beginner_tutorials

Create an src folder to store the source code.

mkdir -p src

Publisher node source code

Create a talker.cpp source file in the src folder:

touch src/talker.cpp 

Then paste the following in the talker.cpp source file:

 #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;
}

Publisher node source code explanation

Let's explain the above source code:
The first is:

#include "ros/ros.h"

ros/ros.h is a convenient include header file, which includes the most commonly used common parts required by all header files in the ROS system.
Next:

#include "std_msgs/String.h"

This is a reference to std_msgs/String message, which is in std_msgs package. This is an example from STD_ The automatically generated header file in the String.msg file in the msgs package.

Initialize the ROS node.

ros::init(argc, argv, "talker");

This allows the ROS to rename from the command line (which is not important now). Here, we only need to know that the name of the specified node is "talker".
Note that in a running system, the name of the node must be unique. At the same time, the name used here is base name, that is, there can be no (/) or (~) characters during naming.

Create a handle to this processing node.

ros::NodeHandle n;

The first node "handle" created will actually initialize the node, while the destruction of the last NodeHandle will clean up the resources used by all nodes.

ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

Tell the master that we are going to publish an STD type through the topic "chatter"_ Msgs:: string message. This will make the master tell all the nodes that are listening to "chatter" that we will publish messages on this topic. The second parameter is the size of our publication queue. In this case, if we publish too fast, it will store up to 1000 data for us before we start discarding "old data".
Nodehandle:: advertisement() returned a ros::Publisher object. It meets two objectives:

  • 1) It contains a publish() method that allows you to publish data to the topic you specify when you create it.
  • 2) When it is out of range, it will automatically stop broadcasting.

The ros::Rate object allows us to specify the frequency of the loop. It tracks the time since the last call to Rate::sleep() and sleeps the corresponding number of times.

ros::Rate loop_rate(10);

Here we want to operate at a frequency of 10Hz.

  int count = 0;
  while (ros::ok())
  {

By default, roscpp will install a SIGINT handler that provides Ctrl + C processing. If Ctrl + C is detected, ros:: ok() will return false.

ros:: ok() will return false in the following cases:

  • SIGINT processor received Ctrl + C
  • We were kicked out of the network by another node with the same name
  • Another program called ros::shutdown()
  • All os::NodeHandles have been destroyed

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

   std_msgs::String msg;

   std::stringstream ss;
   ss << "hello world " << count;
   msg.data = ss.str();

We will use a message adaptation class to publish messages, usually generated from an msg file. You can also have more complex data types, but for now, we will use a standard String message with only one member "data".

The meaning of the above code snippet is to define a standard STD_ String type variable "msg" in msgs. Then define a string stream ss. Keep typing "hello"
"world" and count variables. Then assign the value to the member data of the variable "msg".

Now, we really broadcast this message to the node that receives it.

chatter_pub.publish(msg);

Use the publish() method.

ROS_INFO is a commonly used function instead of printf/cout.

ROS_INFO("%s", msg.data.c_str());

In this simple procedure, calling ros::spinOnce() is not necessary because we have not received any calls. However, if we want to add a subscriber to this program and do not add ros::spinOnce() here, we will never receive a call, so adding this sentence is a better method anyway.

ros::spinOnce()

Finally, we use the loop defined by ros::Rate_ The rate object sleeps for a certain time to ensure our 10Hz release frequency.

loop_rate.sleep();

Here is a brief summary of the above process:

  • Initialize ROS system
  • Broadcast the STD we will post on the "chatter" topic_ Msgs / string message to master
  • Cycle at a frequency of 10 Hz per second

After completing the above work, we need to write a node to receive the message.

Write a Subscriber node

Subscriber node source code

Create a listener.cpp source file in the src folder directory.

Add the following:

#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;
}

Subscriber node source code explanation

Now, let's take a look at the source code fragment of the Subscriber node. What has been explained in the previous Publisher will not be described.

The first is a callback function, which will be called when a new message arrives at chatter topic. The message is through a boost shared_ptr pointers are passed, which means that we can store them without worrying about their deletion and without copying the underlying data.

void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}

Subscribe to the chatter topic through the following commands. When a new message arrives, call the above chatterCallback function. The second parameter is the size of the queue in case we can't process messages fast enough. In this case, if the queue reaches 1000 messages, we will discard the old data when the new message arrives.

ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);

NodeHandle::subscribe() returns a ros::Subscriber object. We must use it until we want to unsubscribe. When the subscriber object is destroyed, it will automatically cancel the subscription to the chatter topic.
Some versions of the subscribe() function allow us to call member functions of a class, or even specify any function that the Boost.function object can call.

At the end of the loop, add a sentence ros::spin();, Call the message callback as soon as possible. Don't worry if you don't use it, because it won't take up too much CPU resources. When ros::ok() returns false, ros::spin() will exit, which means that ros::shutdown() has been called by Ctrl+C or the main program telling us to end or manual operation.

ros::spin();

Finally, let's summarize the process of the whole program:

  • Initialize ROS system
  • Subscribe to "chatter" topics
  • ros::spin(), waiting for the message to arrive
  • When the message arrives, call the callback function chatterCallback().

Compile node

We used catkin before_ create_ PKG command to create beginer_ A package.xml file and a CMakeLists.txt file are automatically generated during the process of tutorials package.
The format of the generated CMakeLists.txt file is as follows:

cmake_minimum_required(VERSION 3.0.2)
project(beginner_tutorials)

## Compile as C++11, supported in ROS Kinetic and newer
# add_compile_options(-std=c++11)

## Find catkin macros and libraries
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
## is used, also find other catkin packages
find_package(catkin REQUIRED COMPONENTS
  roscpp
  rospy
  std_msgs
  message_generation
)

## System dependencies are found with CMake's conventions
# find_package(Boost REQUIRED COMPONENTS system)


## Uncomment this if the package has a setup.py. This macro ensures
## modules and global scripts declared therein get installed
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
# catkin_python_setup()

################################################
## Declare ROS messages, services and actions ##
################################################

## To declare and build messages, services or actions from within this
## package, follow these steps:
## * Let MSG_DEP_SET be the set of packages whose message types you use in
##   your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...).
## * In the file package.xml:
##   * add a build_depend tag for "message_generation"
##   * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET
##   * If MSG_DEP_SET isn't empty the following dependency has been pulled in
##     but can be declared for certainty nonetheless:
##     * add a exec_depend tag for "message_runtime"
## * In this file (CMakeLists.txt):
##   * add "message_generation" and every package in MSG_DEP_SET to
##     find_package(catkin REQUIRED COMPONENTS ...)
##   * add "message_runtime" and every package in MSG_DEP_SET to
##     catkin_package(CATKIN_DEPENDS ...)
##   * uncomment the add_*_files sections below as needed
##     and list every .msg/.srv/.action file to be processed
##   * uncomment the generate_messages entry below
##   * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)

## Generate messages in the 'msg' folder
add_message_files(
   FILES
   Num.msg
 )

## Generate services in the 'srv' folder
add_service_files(
   FILES
   AddTwoInts.srv
 )

## Generate actions in the 'action' folder
# add_action_files(
#   FILES
#   Action1.action
#   Action2.action
# )

## Generate added messages and services with any dependencies listed here
generate_messages(
   DEPENDENCIES
   std_msgs
 )

################################################
## Declare ROS dynamic reconfigure parameters ##
################################################

## To declare and build dynamic reconfigure parameters within this
## package, follow these steps:
## * In the file package.xml:
##   * add a build_depend and a exec_depend tag for "dynamic_reconfigure"
## * In this file (CMakeLists.txt):
##   * add "dynamic_reconfigure" to
##     find_package(catkin REQUIRED COMPONENTS ...)
##   * uncomment the "generate_dynamic_reconfigure_options" section below
##     and list every .cfg file to be processed

## Generate dynamic reconfigure parameters in the 'cfg' folder
# generate_dynamic_reconfigure_options(
#   cfg/DynReconf1.cfg
#   cfg/DynReconf2.cfg
# )

###################################
## catkin specific configuration ##
###################################
## The catkin_package macro generates cmake config files for your package
## Declare things to be passed to dependent projects
## INCLUDE_DIRS: uncomment this if your package contains header files
## LIBRARIES: libraries you create in this project that dependent projects also need
## CATKIN_DEPENDS: catkin_packages dependent projects also need
## DEPENDS: system dependencies of this project that dependent projects also need
catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES beginner_tutorials
   CATKIN_DEPENDS roscpp rospy std_msgs message_runtime
#  DEPENDS system_lib
)

###########
## Build ##
###########

## Specify additional locations of header files
## Your package locations should be listed before other locations
include_directories(
# include
  ${catkin_INCLUDE_DIRS}
)

## Declare a C++ library
# add_library(${PROJECT_NAME}
#   src/${PROJECT_NAME}/beginner_tutorials.cpp
# )

## Add cmake target dependencies of the library
## as an example, code may need to be generated before libraries
## either from message generation or dynamic reconfigure
# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Declare a C++ executable
## With catkin_make all packages are built within a single CMake context
## The recommended prefix ensures that target names across packages don't collide
# add_executable(${PROJECT_NAME}_node src/beginner_tutorials_node.cpp)

## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use
## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node"
# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "")

## Add cmake target dependencies of the executable
## same as for the library above
# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

## Specify libraries to link a library or executable target against
# target_link_libraries(${PROJECT_NAME}_node
#   ${catkin_LIBRARIES}
# )

#############
## Install ##
#############

# all install targets should use catkin DESTINATION variables
# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html

## Mark executable scripts (Python etc.) for installation
## in contrast to setup.py, you can choose the destination
# catkin_install_python(PROGRAMS
#   scripts/my_python_script
#   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )

## Mark executables for installation
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html
# install(TARGETS ${PROJECT_NAME}_node
#   RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# )

## Mark libraries for installation
## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html
# install(TARGETS ${PROJECT_NAME}
#   ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
#   RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
# )

## Mark cpp header files for installation
# install(DIRECTORY include/${PROJECT_NAME}/
#   DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
#   FILES_MATCHING PATTERN "*.h"
#   PATTERN ".svn" EXCLUDE
# )

## Mark other files for installation (e.g. launch and bag files, etc.)
# install(FILES
#   # myfile1
#   # myfile2
#   DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# )

#############
## Testing ##
#############

## Add gtest based cpp test target and link libraries
# catkin_add_gtest(${PROJECT_NAME}-test test/test_beginner_tutorials.cpp)
# if(TARGET ${PROJECT_NAME}-test)
#   target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME})
# endif()

## Add folders to be run by python nosetests
# catkin_add_nosetests(test)

Add the following at the bottom of the CMakeLists.txt file:

add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker beginner_tutorials_generate_messages_cpp)

add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener beginner_tutorials_generate_messages_cpp)

If we know a little about CMake, we will realize that this will create two executable files, talker and listener, which will be put into the devel folder by default. The path is:

 ~/catkin_ws/devel/lib/<package name>

Note that we must also add the target dependency of the executable to the message generation target.

add_dependencies(talker beginner_tutorials_generate_messages_cpp)

This ensures that the package header is generated before using the package. If you use messages from other packages in the catkin workspace, we also need to add dependencies to their respective build targets because catkin builds all projects in parallel. Starting with the Groovy version of ROS, we can use the following variables to rely on all necessary targets:

target_link_libraries(talker ${catkin_LIBRARIES})

Next, use catkin_make to compile and generate the executable we want.

# In your catkin workspace
$ cd ~/catkin_ws
$ catkin_make  

After compiling, we start roscore, then open two terminal windows, and use rosrun to start talker and listener nodes respectively. The commands are as follows:

$ roscore  

Start talker

$ rosrun beginner_tutorials talker

Start listener

rosrun beginner_tutorials listener

Output results:

Of course, we can also use ~ / catkin_ ws/devel/lib/beginner_ Find the generated executable file in the tutorials folder and directly call the two files to run the node.

./talker
./listener

Tags: ROS Autonomous vehicles

Posted on Tue, 16 Nov 2021 10:01:51 -0500 by surion