Tutorial 3: Communications using topics

Objective: This tutorial session is devoted to learn the ROS publish/subscribe anonymous message passing.

Contents

After this session you will be able to:

  • Understand the publishing mechanism: the publisher object and the publishing loop.

  • Understand the subscriber mechanism: the subscriber object, the callback functions and how to grant their execution.

  • Write nodes able to publish/subscribe to messages.

  • Use the rqt message type browser tool to look for the standard and common message types.

Provided packages:

This tutorial will use the following package:

Package agitr_chapter3

Prepare the catkin workspace to work with it:

  1. If you have not yet done so, follow the steps in Exercise 0 to:

  • clone in your ~/git-projects folder the meta-repository containing all the required packages,

  • create the catkin workspace folders (use catkin_ws3 for the current tutorial).

  1. Follow the procedure shown in Procedure to prepare the exercises to import in the intro2ros/2023/teamXX/EXERCISE1 subgroup of your GitLab account the packages:

  1. In your git-projects folder clone the agitr_chapter3 and solution_exercise1 projects of your GitLab account inside an exercise1 subfolder (change XX by your team number). You can use https:

$ git clone https://gitlab.com/intro2ros/2023/teamXX/exercise1/agitr_chapter3.git exercise1/agitr_chapter3
$ git clone https://gitlab.com/intro2ros/2023/teamXX/exercise1/solution_exercise1.git exercise1/solution_exercise1

or ssh:

$ git clone git@gitlab.com:intro2ros/2023/teamXX/exercise1/agitr_chapter3.git exercise1/agitr_chapter3
$ git clone git@gitlab.com:intro2ros/2023/teamXX/exercise1/solution_exercise1.git exercise1/solution_exercise1
  1. In your catkin_ws3/src folder create a symbolic link to the package to be used:

$ ln -s ~/git-projects/exercise1/agitr_chapter3
  1. Edit the build.sh file of the solution_exercise1 package to add your credencials (token_user and token_code) and push the change.

References:

This tutorial is partially based on chapter 3 of A gentle introduction ROS by Jason M. O’Kane.

Complementary resources:

The package agitr_chapter3

This package contains:

  • The program pubvel.cpp, whose executable file called pubvel creates a node called publish_velocity. This node broadcasts random command velocities to be used by the turtlesim node to move a turtle.

  • The program subpose.cpp, whose executable file called subpose creates a node called subscribe_to_pose. This node subscribes to the pose messages send by the turtle of the turtlesim package, and prints it in the terminal.

Take a look at the package.xml file:

  • Note that because pubvel uses a message type from the geometry_msgs package, we must declare a dependency on that package.

  • Note that because subpose uses a message type from the turtlesim package, we must declare a dependency on that package.

<package format="2">
  <name>agitr_chapter3</name>
  <version>0.0.1</version>
  <description>
    Examples from A Gentle Introduction to ROS
  </description>
  <maintainer email="jokane@cse.sc.edu">
    Jason O'Kane
  </maintainer>
  <license>TODO</license>
  <buildtool_depend>catkin</buildtool_depend>
  <depend>geometry_msgs</depend>
  <depend>turtlesim</depend>
</package>

Take a look at the CMakeLists.txt file:

  • Note that the find_package includes geometry_msgs.

  • Note that the find_package includes turtlesim.

# What version of CMake is needed?
cmake_minimum_required(VERSION 2.8.3)

# The name of this package.
project(agitr_chapter3)

# Find the catkin build system, and any other packages on which we depend.
find_package(catkin REQUIRED COMPONENTS roscpp geometry_msgs turtlesim)

# Declare our catkin package.
catkin_package()

# Specify locations of header files.
include_directories(include ${catkin_INCLUDE_DIRS})

# Declare the executable, along with its source files.
add_executable(pubvel src/pubvel.cpp)
add_executable(subpose src/subpose.cpp)

# Specify libraries against which to link.
target_link_libraries(pubvel ${catkin_LIBRARIES})
target_link_libraries(subpose ${catkin_LIBRARIES})

A publisher program

The pubvel.cpp program randomly generates command velocities to be sent to the turtlesim.

Run the turtlesim:

$ rosrun turtlesim turtlesim_node

Compile and execute the pubvel program

$ catkin build agitr_chapter3
$ source devel/setup.bash
$ rosrun agitr_chapter3 pubvel

Exercise

Check the nodes using rosnode and rqt_graph.

The code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
 // This program publishes randomly-generated velocity
 // messages for turtlesim.
 #include <ros/ros.h>
 #include <geometry_msgs/Twist.h>  // For geometry_msgs::Twist
 #include <stdlib.h> // For rand() and RAND_MAX

 int main(int argc, char **argv) {
   // Initialize the ROS system and become a node.
   ros::init(argc, argv, "publish_velocity");
   ros::NodeHandle nh;

   // Create a publisher object.
   ros::Publisher pub = nh.advertise<geometry_msgs::Twist>(
     "turtle1/cmd_vel", 1000);

   // Seed the random number generator.
   srand(time(0));

   // Loop at 2Hz until the node is shut down.
   ros::Rate rate(2);
   while(ros::ok()) {
     // Create and fill in the message.  The other four
     // fields, which are ignored by turtlesim, default to 0.
     geometry_msgs::Twist msg;
     msg.linear.x = double(rand())/double(RAND_MAX);
     msg.angular.z = 2*double(rand())/double(RAND_MAX) - 1;

     // Publish the message.
     pub.publish(msg);

     // Send a message to rosout with the details.
     ROS_INFO_STREAM("Sending random velocity command:"
       << " linear=" << msg.linear.x
       << " angular=" << msg.angular.z);

     // Wait until it's time for another iteration.
     rate.sleep();
   }
 }

Publishing messages

  1. The message type: Each topic is associated with a message type, and the corresponding header file has to be included. In this case:

    #include<geometry_msgs/Twist.h>

where geometry_msg is the package containing the message type.

  1. The publisher object: The work of actually publishing the messages is done by an object of class ros::Publisher.

    ros::Publisher pub = node_handle.advertise<message_type>(topic_name, queue_size);

where:

  • The node_handle is an object of class ros::NodeHandle, nh in the example.

  • The message_type part inside the angle brackets — formally called the template parameter — is the data type for the messages we want to publish, geometry::msgs::Twist in the example.

  • The topic_name is a string containing the name of the topic on which we want to publish (the leading slash has been dropped to make the topic name a relative name), turtle1/cmd_vel in the example.

  • The queue_size is the size of the queue where messages are stored. A separate thread actually transmits them. A reasonable large valeu (e.g. 1000) is usually suitable (if your program rapidly publishes more messages than the queue can hold, the oldest unsent messages will be discarded).

If you want to publish messages on multiple topics from the same node, you’ll need to create a separate ros::Publisher object for each topic. Create them once and use those publishers throughout the execution of your program when needed.

  1. The message object: The message object is created and filled with the data to be broadcasted, in the example the linear.x and the angular.z fields of the geometry_msgs::Twist message type (field values are defaulted to zero).

  2. Publish the message: Finally, the publisher object publishes the message object.

    pub.publish(msg)

Exercise

  • Check the topic type and the message info.

  • Echo the messages being broadcasted.

The publishing loop

  1. Loop control: The publishing loop is controlled by the condition:

ros::ok()

Informally, this function checks whether the node has not yet been shut down by a rosnode kill command or by a Ctrl-C interrupt signal.

  1. Publishing rate:

A ros::Rate object is created to control the publishing rate. It is set in Hz units. At the end of the loop, a sleep method of this object is called to complete the time established per loop iteration.

Exercise

Verify the publishing frequency

A subscriber program

The subpose.cpp program creates the node /subscribe_to_pose that subscribes to the /turtle1/pose topic, on which /turtlesim node publishes.

Compile and execute the subpose program

$ rosrun agitr_chapter3 subpose

Exercise

  • Check the nodes using the rosnode and rqt_graph commands.

  • Verify that /turtlesim node is publishing this topic and that /subscribe_to_pose node is subscribed to it.

The code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   // This program subscribes to turtle1/pose and shows its
   // messages on the screen.
   #include <ros/ros.h>
   #include <turtlesim/Pose.h>
   #include <iomanip> // for std::setprecision and std::fixed

   // A callback function.  Executed each time a new pose
   // message arrives.
   void poseMessageReceived(const turtlesim::Pose& msg) {
     ROS_INFO_STREAM(std::setprecision(2) << std::fixed
       << "position=(" <<  msg.x << "," << msg.y << ")"
       << " direction=" << msg.theta);
   }

   int main(int argc, char **argv) {
     // Initialize the ROS system and become a node.
     ros::init(argc, argv, "subscribe_to_pose");
     ros::NodeHandle nh;

     // Create a subscriber object.
     ros::Subscriber sub = nh.subscribe("turtle1/pose", 1000,
       &poseMessageReceived);

     // Let ROS take over.
     ros::spin();
   }

The callback function

Any code that responds to incoming messages (whose arrival time is unknown) is placed inside a callback function, which ROS calls once for each arriving message.

void function_name(const package_name::type_name &msg)

The package_name and type_name refer to the message class for the topic to which we plan to subscribe.

The body of the callback function then has access to all of the fields in the received message, and can store, use, or discard that data as it sees fit.

The appropriate header that defines the message has to be included, e.g. #include <turtlesim/Pose.h>

The subscriber object

To subscribe to a topic, a ros::Subscriber object has to be created:

ros::Subscriber sub = node_handle.subscribe(topic_name, queue_size, pointer_to_callback_function)

Where:

  • The node_handle is an object of class ros::NodeHandle, nh in the example.

  • The topic_name is the name of the topic to which we want to subscribe, in the form of a string, turtle1/pose in the example.

  • The queue_size is the integer size of the message queue for this subscriber (when a new message arrives, it is stored in a queue until ROS gets a chance to execute your callback function). If new messages arrive when the queue is full, the oldest unprocessed messages will be dropped to make room. The speed with which ROS empties a subscribing queue depends on how quickly we process callbacks.

  • The pointer_to_callback_function is the pointer to the callback function, &poseMessageReceived in the example.

Attention

If you use the wrong message type as the argument to your callback function, the compiler will not be able to detect this error. Instead, you’ll see run-time error messages complaining about the type mismatch. These errors could, depending on the timing, come from either the publisher or subscriber nodes.

Giving ROS control

ROS will only execute our callback function when we give it explicit permission to do so. There are actually two slightly different ways to accomplish this, using ros::spinOnce() or ros::spin().

  1. ros::spinOnce() asks ROS to execute all of the pending callbacks from all of the node’s subscriptions, and then return control back to us.

  2. ros::spin() asks ROS to wait for and execute callbacks until the node shuts down, i.e. it is roughly equivalent to this loop:

    while(ros::ok()){
    ros::spinOnce();
    }

Attention

If you mistakenly omit them, ROS won’t have the opportunity to execute your callback function…

Standard and common messages

Standard messages

Standard messages (std_msgs) include common message types representing primitive data types and other basic message constructs, such as multiarrays:

  • Bool

  • Byte

  • ByteMultiArray

  • Char

  • ColorRGBA

  • Duration

  • Empty

  • Float32

  • Float32MultiArray

  • Float64

  • Float64MultiArray

  • HeaderCheck your run_id folder and browse the rosout.log file

  • Int16

  • Int16MultiArray

  • Int32

  • Int32MultiArray

  • Int64

  • Int64MultiArray

  • Int8

  • Int8MultiArray

  • MultiArrayDimension

  • MultiArrayLayout

  • String

  • Time

  • UInt16

  • UInt16MultiArray

  • UInt32

  • UInt32MultiArray

  • UInt64

  • UInt64MultiArray

  • UInt8

  • UInt8MultiArray

Exercise

Use the rqt tool to browse the std_msgs types:

$ rqt -s rqt_msg
../_images/rqtmsg.png

Common messages

Common messages (common_msgs) group messages that are widely used by other ROS packages, i.e. messages for:

Geometry messages

geometry_msgs provides messages for common geometric primitives such as points, vectors, and poses. These primitives are designed to provide a common data type and facilitate interoperability throughout the system.

  • Accel

  • AccelStamped

  • AccelWithCovariance

  • AccelWithCovarianceStampedmessage

  • Inertia

  • InertiaStamped

  • Point

  • Point32

  • PointStamped

  • Polygon

  • PolygonStamped

  • Pose

  • Pose2D

  • PoseArray

  • PoseStamped

  • PoseWithCovariance

  • PoseWithCovarianceStamped

  • Quaternion

  • QuaternionStamped

  • Transform

  • TransformStamped

  • Twist

  • TwistStamped

  • TwistWithCovariance

  • TwistWithCovarianceStamped

  • Vector3

  • Vector3Stamped

  • Wrench

  • WrenchStamped

Exercise

Use the rqt tool to browse the geometry_msgs types:

$ rqt -s rqt_msg
../_images/rqtmsg_geometry.png

Common sensors

This package defines messages for commonly used sensors, including cameras and scanning laser range finders.

  • CameraInfo

  • ChannelFloat32

  • CompressedImage

  • FluidPressure

  • Illuminance

  • Image

  • Imu

  • JointState

  • Joy

  • JoyFeedback

  • JoyFeedbackArray

  • LaserEcho

  • LaserScan

  • MagneticField

  • MultiDOFJointState

  • MultiEchoLaserScan

  • NavSatFix

  • NavSatStatus

  • PointCloud

  • PointCloud2

  • PointField

  • Range

  • RegionOfInterest

  • RelativeHumidity

  • Temperature

  • TimeReference

Exercise

Use the rqt tool to browse the sensor_msgs types:

$ rqt -s rqt_msg
../_images/rqtmsg_sensors.png

EXERCISE 1

To solve Exercise 1 you must modify the agitr_chapter3 package that you have cloned in your git-projects folder.

Recall to keep commiting the changes when coding the solution (do small commits with good commit messages!) and write a README.md file in the solution_exercise1 package explaining your solution (Markdown Cheat Sheet).

Exercise

Code a new node called pubvelsafe (in a new file pubvelsafe.cpp) in order to send:

  1. A random angular velocity command and a fixed linear velocity of 1.0 when the turtle is placed in a safe zone defined by a square around the center of the window.

  2. A random angular velocity and a random linear velocity (i.e. as done in pubvel.cpp) otherwise.

Exercise1b. Optional

Code a new node called pubvelsafe_b (in a new file pubvelsafe_b.cpp) to make the turtle smarter not to get stuck to the walls for long.

SOLUTION EXERCISE 1

Download the zipped solution.