Understanding ROS community level
These are ROS resources that enable a new community for ROS to exchange software and knowledge. The various resources in these communities are as follows:
- Distributions: Similar to the Linux distribution, ROS distributions are a collection of versioned meta packages that we can install. The ROS distribution enables easier installation and collection of the ROS software. The ROS distributions maintain consistent versions across a set of software.
- Repositories: ROS relies on a federated network of code repositories, where different institutions can develop and release their own robot software components.
- The ROS Wiki: The ROS community Wiki is the main forum for documenting information about ROS. Anyone can sign up for an account and contribute their own documentation, provide corrections or updates, write tutorials, and more.
- Bug ticket system: If we find a bug in the existing software or need to add a new feature, we can use this resource.
- Mailing lists: The ROS-users mailing list is the primary communication channel about new updates to ROS, as well as a forum to ask questions about the ROS software.
- ROS Answers: This website resource helps to ask questions related to ROS. If we post our doubts on this site, other ROS users can see this and give solutions.
- Blog: The ROS blog updates with news, photos, and videos related to the ROS community (http://www.ros.org/news).
What are the prerequisites to start with ROS?
Before getting started with ROS and trying the code in this book, the following prerequisites should be met:
- Ubuntu 14.04.2 LTS / Ubuntu 15.04: We have to use Ubuntu as the operating system for installing ROS. We prefer to stick on to the L.T.S version of Ubuntu, that is, Ubuntu 14.04/14.04.3, or if you want to explore new ROS distribution you can use Ubuntu 15.04.
- ROS Jade/Indigo desktop full installation: Install the full desktop installation of ROS. The version we prefer is ROS Indigo, the latest version, Jade, is also supported. The following link gives you the installation instruction of the latest ROS distribution: http://wiki.ros.org/indigo/Installation/Ubuntu.
Running ROS Master and ROS parameter server
Before running any ROS nodes, we should start ROS Master and the ROS parameter server. We can start ROS Master and the ROS parameter server using a single command called roscore
, which will start the following programs:
- ROS Master
- ROS parameter server
- rosout logging nodes
The rosout
node will collect log messages from other ROS nodes and store them in a log file, and will also rebroadcast the collected log message to another topic. The topic /rosout
is published by ROS nodes working using ROS client libraries such as roscpp
and rospy
and this topic is subscribed by the rosout
node which rebroadcasts the message in another topic called /rosout_agg
. This topic has an aggregate stream of log messages. The command roscore
is a prerequisite before running any ROS node. The following screenshot shows the messages printing when we run the roscore
command in a terminal.
The following is a command to run roscore
on a Linux terminal:
$ roscore
The following are explanations of each section when executing roscore
on the terminal:
- In the first section, we can see a log file is creating inside the
~/.ros/log
folder for collecting logs from ROS nodes. This file can be used for debugging purposes. - In the second section, the command starts a ROS launch file called
roscore.xml
. When a launch file starts, it automatically starts therosmaster
and ROS parameter server. Theroslaunch
command is a Python script, which can startrosmaster
and the ROS parameter server whenever it tries to execute a launch file. This section shows the address of the ROS parameter server within the port. - In the third section, we can see the parameters such as
rosdistro
androsversion
displayed on the terminal. These parameters are displayed when it executesroscore.xml
. We can see more onroscore.xml
and its details in the next section. - In the fourth section, we can see the
rosmaster
node is started usingROS_MASTER_URI
, which we defined earlier as an environment variable. - In the fifth section, we can see the
rosout
node is started, which will start subscribing the/rosout
topic and rebroadcasting into/rosout_agg
.
The following is the content of roscore.xml
:
<launch> <group ns="/"> <param name="rosversion" command="rosversion roslaunch" /> <param name="rosdistro" command="rosversion -d" /> <node pkg="rosout" type="rosout" name="rosout" respawn="true"/> </group> </launch>
When the roscore
command is executed, initially, the command checks the command line argument for a new port number for the rosmaster
. If it gets the port number, it will start listening to the new port number, otherwise it will use the default port. This port number and the roscore.xml
launch file will pass to the roslaunch
system. The roslaunch
system is implemented in a Python module, it will parse the port number and launch the roscore.xml
file.
In the roscore.xml
file, we can see the ROS parameters and nodes are encapsulated in a group XML tag with a "/
" namespace. The group XML tag indicates that all the nodes inside this tag have the same settings.
The two parameters called rosversion
and rosdistro
store the output of the rosversion
roslaunch
and rosversion
-d
commands using the command
tag, which is a part of the ROS param
tag. The command tag will execute the command mentioned on it and store the output of the command in these two parameters.
The rosmaster
and parameter server are executed inside roslaunch
modules by using the ROS_MASTER_URI
address. This is happening inside the roslaunch
Python module. The ROS_MASTER_URI
is a combination of the IP address and port in which rosmaster
is going to listen. The port number can be changed according to the given port number in the roscore
command.
Checking the roscore command output
Let's check the ROS topics and ROS parameters created after running roscore
. The following command will list the active topics on the terminal:
$ rostopic list
The list of topics are as follows, as per our discussion on the rosout
node subscribe /rosout
topic, which have all log messages from the ROS nodes and /rosout_agg
rebroadcast the log messages:
/rosout /rosout_agg
The following command lists out the parameters available when running roscore
. The following is the command to list the active ROS parameter:
$ rosparam list
The parameters are mentioned here; they have the ROS distribution name, version, address of roslaunch
server and run_id
, where run_id
is a unique ID associated with a particular run of roscore
:
/rosdistro /roslaunch/uris/host_robot_virtualbox__51189 /rosversion /run_id
The list of the ROS service generated during the running roscore
can be checked using the following command:
$ rosservice list
The list of services running is as follows:
/rosout/get_loggers /rosout/set_logger_level
These ROS services are generated for each ROS node for setting the logging levels.
After understanding the basics of ROS Master, Parameter server, and roscore
we can go to the procedure to build a ROS package. Along with working with the ROS package, we can refresh the concepts of ROS nodes, topics, messages, services, and actionlib.
Creating a ROS package
The ROS packages are the basic unit of the ROS system. We can create the ROS package, build it and release it to the public. The current distribution of ROS we are using is Jade/Indigo. We are using the catkin
build system to build ROS packages. A build system is responsible for generating 'targets'
(executable/libraries) from a raw source code that can be used by an end user. In older distributions, such as Electric and Fuerte, rosbuild
was the build system. Because of the various flaws of rosbuild
, catkin
came into existence, which is basically based on
CMake (Cross Platform Make). This has lot of advantages such as porting the package into other operating system, such as Windows. If an OS supports CMake and Python, catkin
based packages can be easily ported into it.
The first requirement in creating ROS packages is to create a ROS catkin
workspace. Here is the procedure to build a catkin
workspace.
Build a workspace folder in the home directory and create a src
folder inside the workspace folder:
$ mkdir ~/catkin_ws/src
Switch to the source folder. The packages are created inside this package:
$cd ~/catkin_ws/src
Initialize a new catkin
workspace:
$ catkin_init_workspace
We can build the workspace even if there are no packages. We can use the following command to switch to the workspace folder:
$ cd ~/catkin_ws
The catkin_make
command will build the following workspace:
$ catkin_make
After building the empty workspace, we should set the environment of the current workspace to be visible by the ROS system. This process is called overlaying a workspace. We should add the package environment using the following command:
$ echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc $ source ~/.bashrc
This command will source a bash script called setup.bash
inside the devel
workspace folder. To set the environment in all bash sessions, we need to add a source command in the .bashrc
file, which will source this script whenever a bash session starts.
This is the link of the procedure http://wiki.ros.org/catkin/Tutorials/create_a_workspace.
- After setting the
catkin
workspace, we can create our own package that has sample nodes to demonstrate the working of ROS topics, messages, services, and actionlib. - The
catkin_create_pkg
command is used to create a ROS package. This command is used to create our package in which we are going to create demos of various ROS concepts. - Switch to the
catkin
workspacesrc
folder and create the package using the following command:Syntax of catkin_create_pkg : catkin_create_pkg [package_name] [dependency1] [dependency2]
- Here is the command to create the sample ROS package:
$ catkin_create_pkg mastering_ros_demo_pkg roscpp std_msgs actionlib actionlib_msgs
The dependencies in the packages are as follows:
- roscpp: This is the C++ implementation of ROS. It is a ROS client library which provides APIs to C++ developers to make ROS nodes with ROS topics, services, parameters, and so on. We are including this dependency because we are going to write a ROS C++ node. Any ROS package which uses the C++ node must add this dependency.
- std_msgs: This package contains basic ROS primitive data types such as integer, float, string, array, and so on. We can directly use these data types in our nodes without defining a new ROS message.
- actionlib: The actionlib meta-package provides interfaces to create preemptable tasks in ROS nodes. We are creating actionlib based nodes in this package. So we should include this package to build the ROS nodes.
- actionlib_msgs: This package contains standard message definitions needed to interact with the action server and action client.
We will get the following message if the package is successfully created:
- After creating this package, build the package without adding any nodes using the
catkin_make
command. This command must be executed from thecatkin
workspace path. The following command shows you how to build our empty ROS package:~/catkin_ws$ catkin_make
- After a successful build, we can start adding nodes to the
src
folder of this package.
The build folder in the CMake build files mainly contains executables of the nodes that are placed inside the catkin
workspace src
folder. The devel
folder contains bash script, header files, and executables in different folders generated during the build process. We can see how to make ROS nodes and build using catkin_make
.
Working with ROS topics
Topics are the basic way of communicating between two nodes. In this section, we can see how the topics works. We are going to create two ROS nodes for publishing a topic and subscribing the same. Navigate to the chapter_1_codes/mastering_ros_demo_package/src
folder for the codes. demo_topic_publisher.cpp
and demo_topic_subscriber.cpp
are the two sets of code that we are going to discuss.
Creating ROS nodes
The first node we are going to discuss is demo_topic_publisher.cpp
. This node will publish an integer value on a topic called /numbers
. Copy the current code into a new package or use this existing file from the code repository.
Here is the complete code:
#include "ros/ros.h" #include "std_msgs/Int32.h" #include <iostream> int main(int argc, char **argv) { ros::init(argc, argv,"demo_topic_publisher"); ros::NodeHandle node_obj; ros::Publisher number_publisher = node_obj.advertise<std_msgs::Int32>("/numbers",10); ros::Rate loop_rate(10); int number_count = 0; while (ros::ok()) { std_msgs::Int32 msg; msg.data = number_count; ROS_INFO("%d",msg.data); number_publisher.publish(msg); ros::spinOnce(); loop_rate.sleep(); ++number_count; } return 0; }
Here is the detailed explanation of the preceding code:
#include "ros/ros.h" #include "std_msgs/Int32.h" #include <iostream>
The ros/ros.h
is the main header of ROS. If we want to use the roscpp
client APIs in our code, we should include this header. The std_msgs/Int32.h
is the standard message definition of integer datatype.
Here, we are sending an integer value through a topic. So we should need a message type for handling the integer data. std_msgs
contains standard message definition of primitive datatypes. std_msgs/Int32.h
contains integer message definition:
ros::init(argc, argv,"demo_topic_publisher");
This code will initialize a ROS node with a name. It should be noted that the ROS node should be unique. This line is mandatory for all ROS C++ nodes:
ros::NodeHandle node_obj;
This will create a Nodehandle
object, which is used to communicate with the ROS system:
ros::Publisher number_publisher = node_obj.advertise<std_msgs::Int32>("/numbers",10);
This will create a topic publisher and name the topic /numbers
with a message type std_msgs::Int32
. The second argument is the buffer size. It indicates that how many messages need to be put in a buffer before sending. It should be set to high if the data sending rate is high:
ros::Rate loop_rate(10);
This is used to set the frequency of sending data:
while (ros::ok()) {
This is an infinite while loop, and it quits when we press Ctrl+C. The ros::ok()
function returns zero when there is an interrupt; this can terminate this while loop:
std_msgs::Int32 msg; msg.data = number_count;
The first line creates an integer ROS message and the second line assigns an integer value to the message. Here, data is the field name of the msg
object:
ROS_INFO("%d",msg.data);
This will print the message data. This line is used to log the ROS information:
number_publisher.publish(msg);
This will publish the message to the topics /numbers
:
ros::spinOnce();
This command will read and update all ROS topics. The node will not publish without a spin()
or spinOnce()
function:
loop_rate.sleep();
This line will provide the necessary delay to achieve a frequency of 10Hz.
After discussing the publisher node, we can discuss the subscriber node, which is demo_topic_subscriber.cpp
. Copy the code to a new file or use the existing file.
Here is the definition of the subscriber node:
#include "ros/ros.h" #include "std_msgs/Int32.h" #include <iostream> void number_callback(const std_msgs::Int32::ConstPtr& msg) { ROS_INFO("Received [%d]",msg->data); } int main(int argc, char **argv) { ros::init(argc, argv,"demo_topic_subscriber"); ros::NodeHandle node_obj; ros::Subscriber number_subscriber = node_obj.subscribe("/numbers",10,number_callback); ros::spin(); return 0; }
Here is the code explanation:
#include "ros/ros.h" #include "std_msgs/Int32.h" #include <iostream>
This is the header needed for the subscribers:
void number_callback(const std_msgs::Int32::ConstPtr& msg) { ROS_INFO("Recieved [%d]",msg->data); }
This is a callback function that will execute whenever a data comes to the /numbers
topic. Whenever a data reaches this topic, the function will call and extract the value and print it on the console:
ros::Subscriber number_subscriber = node_obj.subscribe("/numbers",10,number_callback);
This is the subscriber and here, we are giving the topic name needed to subscribe, buffer size, and the callback function. We are subscribing /numbers
topic and we have already seen the callback function in the preceding section:
ros::spin();
This is an infinite loop in which the node will wait in this step. This code will fasten the callbacks whenever a data reaches the topic. The node will quit only when we press the Ctrl+C key.
Building the nodes
We have to edit the CMakeLists.txt
file in the package to compile and build the source code. Navigate to chapter_1_codes/mastering_ros_demo_package/CMakeLists.txt
to view the existing CMakeLists.txt
file. The following code snippet in this file is responsible for building these two nodes:
include_directories( include ${catkin_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} ) #This will create executables of the nodes add_executable(demo_topic_publisher src/demo_topic_publisher.cpp) add_executable(demo_topic_subscriber src/demo_topic_subscriber.cpp) #This will generate message header file before building the target add_dependencies(demo_topic_publisher mastering_ros_demo_pkg_generate_messages_cpp) add_dependencies(demo_topic_subscriber mastering_ros_demo_pkg_generate_messages_cpp) #This will link executables to the appropriate libraries target_link_libraries(demo_topic_publisher ${catkin_LIBRARIES}) target_link_libraries(demo_topic_subscriber ${catkin_LIBRARIES})
We can add the preceding snippet to create a new a CMakeLists.txt
file for compiling the two codes.
The catkin_make
command is used to build the package.
We can first switch to workspace:
$ cd ~/catkin_ws
Build mastering_ros_demo_package
as follows:
$ catkin_make mastering_ros_demo_package
We can either use the preceding command to build a specific package or just caktin_make
to build the entire workspace.
This will create executables in ~/catkin_ws/devel/lib/<package name>
.
If the building is done, we can execute the nodes.
First start roscore
:
$ roscore
Now run both commands in two shells.
In the running publisher:
$ rosrun mastering_ros_demo_package demo_topic_publisher
In the running subscriber:
$ rosrun mastering_ros_demo_package demo_topic_subscriber
We can see the output as shown here:
The following diagram shows how the nodes communicate with each other. We can see the demo_topic_publisher
node publish the /numbers
topic and subscribe by then demo_topic_subscriber
node.
We can use the rosnode
and rostopic
tools to debug and understand the working of two nodes:
$ rosnode list
: This will list the active nodes$ rosnode info demo_topic_publisher
: This will get the info of the publisher node$ rostopic echo /numbers
: This will display the value sending through the/numbers
topic$ rostopic type /numbers
: This will print the message type of the/numbers
topic
Adding custom msg and srv files
In this section, we can see how to create custom messages and services definitions in the current package. The message definitions are stored in a .msg
file and service definition are stored in a srv
file. These definitions inform ROS about the type of data and name of data to be transmitted from a ROS node. When a custom message is added, ROS will convert the definitions into equivalent C++ codes, which we can include in our nodes.
We can start with message definitions.
Message definitions have to be written in the .msg
file and have to be kept in the msg
folder, which is inside the package.
We are going to create a message file called demo_msg.msg
with the following definition:
string greeting int32 number
Until now, we have worked only with standard message definitions. Now, we have created our own definitions and can see how to use them in our code.
The first step is to edit the package.xml
file of the current package and uncomment the lines <build_depend>message_generation</build_depend>
and <run_depend>message_runtime</run_depend>
.
Edit the current CMakeLists.txt
and add the message_generation
line as follows:
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs actionlib actionlib_msgs message_generation )
Uncomment the following line and add the custom message file:
add_message_files( FILES demo_msg.msg ) ## Generate added messages and services with any dependencies listed here generate_messages( DEPENDENCIES std_msgs actionlib_msgs )
After these steps, we can compile and build the package:
$ cd ~/catkin_ws/ $ catkin_make
To check whether the message is built properly, we can use the rosmsg
command:
$ rosmsg show mastering_ros_demo_pkg/demo_msg
If the content shown by the command and the definition are the same, the procedure is correct.
If we want to test the custom message, we can build a publisher and subscriber using the custom message type named demo_msg_publisher.cpp
and demo_msg_subscriber.cpp
. Navigate to the chapter_1_codes/mastering_ros_demo_pkg/src
folder for these codes.
We can test the message by adding the following lines of code in CMakeLists.txt
:
add_executable(demo_msg_publisher src/demo_msg_publisher.cpp) add_executable(demo_msg_subscriber src/demo_msg_subscriber.cpp) add_dependencies(demo_msg_publisher mastering_ros_demo_pkg_generate_messages_cpp) add_dependencies(demo_msg_subscriber mastering_ros_demo_pkg_generate_messages_cpp) target_link_libraries(demo_msg_publisher ${catkin_LIBRARIES}) target_link_libraries(demo_msg_subscriber ${catkin_LIBRARIES})
Build the package using catkin_make
and test the node using the following commands.
- Run
roscore
:$ roscore
- Start the custom message publisher node:
$ rosrun mastering_ros_demo_pkg demo_msg_publisher
- Start the custom message subscriber node:
$ rosrun mastering_ros_demo_pkg demo_msg_subscriber
The publisher node publishes a string along with an integer, and the subscriber node subscribes the topic and prints the values. The output and graph are shown as follows:
The topic in which the nodes are communicating is called /demo_msg_topic
. Here is the graph view of two nodes:
Next, we can add srv
files to the package. Create a new folder called srv
in the current package folder and add a srv
file called demo_srv.srv
. The definition of this file is as follows:
string in --- string out
Here, both the Request
and Response
are strings.
In the next step, we need to uncomment the following lines in package.xml
as we did for the ROS messages:
<build_depend>message_generation</build_depend> <run_depend>message_runtime</run_depend>
Take CMakeLists.txt
and add message_runtime
in catkin_package()
:
catkin_package( CATKIN_DEPENDS roscpp rospy std_msgs actionlib actionlib_msgs message_runtime )
We need to follow the same procedure in generating services as we did for the ROS message. Apart from that, we need additional sections to be uncommented as shown here:
## Generate services in the 'srv' folder add_service_files( FILES demo_srv.srv )
After making these changes, we can build the package using catkin_make
and using the following command we can verify the procedure:
$ rossrv show mastering_ros_demo_pkg/demo_srv
If we see the same content as we defined in the file, we can confirm it's working.
Working with ROS services
In this section, we are going to create ROS nodes, which can use the services definition that we defined already. The service nodes we are going to create can send a string message as a request to the server and the server node will send another message as a response.
Navigate to chapter_1_codes/mastering_ros_demo_pkg/src
and find nodes with the names demo_service_server.cpp
and demo_service_client.cpp
.
The demo_service_server.cpp
is the server and its definition is as follows:
#include "ros/ros.h" #include "mastering_ros_demo_pkg/demo_srv.h" #include <iostream> #include <sstream> using namespace std; bool demo_service_callback(mastering_ros_demo_pkg::demo_srv::Request &req, mastering_ros_demo_pkg::demo_srv::Response &res) { std::stringstream ss; ss << "Received Here"; res.out = ss.str(); ROS_INFO("From Client [%s], Server says [%s]",req.in.c_str(),res.out.c_str()); return true; } int main(int argc, char **argv) { ros::init(argc, argv, "demo_service_server"); ros::NodeHandle n; ros::ServiceServer service = n.advertiseService("demo_service", demo_service_callback); ROS_INFO("Ready to receive from client."); ros::spin(); return 0; }
Let's see the explanation of the code:
#include "ros/ros.h" #include "mastering_ros_demo_pkg/demo_srv.h" #include <iostream> #include <sstream>
Here, we included ros/ros.h
, which is a mandatory header for a ROS CPP node. The mastering_ros_demo_pkg/demo_srv.h
header is a generated header, which contains our service definition and can use this in our code. The sstream.h
is for getting string streaming classes:
bool demo_service_callback(mastering_ros_demo_pkg::demo_srv::Request &req, mastering_ros_demo_pkg::demo_srv::Response &res) {
This is the server callback function executed when a request is received on the server. The server can receive the request from clients having a message type of mastering_ros_demo_pkg::demo_srv::Request
and sends the response in the mastering_ros_demo_pkg::demo_srv::Response
type:
std::stringstream ss; ss << "Received Here"; res.out = ss.str();
In this code, the string data "Received Here"
is passing to the service Response
instance. Here, out
is the field name of the response that we have given in the demo_srv.srv
. This response will go to the service client node:
ros::ServiceServer service = n.advertiseService("demo_service", demo_service_callback);
This creates a service having a name as demo_service
and a callback function is executed when a request comes to this service. The callback function is demo_service_callback
, which we saw in the preceding section.
Next, we can see how the demo_service_client.cpp
is working.
Here is the definition of this code:
#include "ros/ros.h" #include <iostream> #include "mastering_ros_demo_pkg/demo_srv.h" #include <iostream> #include <sstream> using namespace std; int main(int argc, char **argv) { ros::init(argc, argv, "demo_service_client"); ros::NodeHandle n; ros::Rate loop_rate(10); ros::ServiceClient client = n.serviceClient<mastering_ros_demo_pkg::demo_srv>("demo_service"); while (ros::ok()) { mastering_ros_demo_pkg::demo_srv srv; std::stringstream ss; ss << "Sending from Here"; srv.request.in = ss.str(); if (client.call(srv)) { ROS_INFO("From Client [%s], Server says [%s]",srv.request.in.c_str(),srv.response.out.c_str()); } else { ROS_ERROR("Failed to call service"); return 1; } ros::spinOnce(); loop_rate.sleep(); } return 0; }
Let's explain the code:
ros::ServiceClient client = n.serviceClient<mastering_ros_demo_pkg::demo_srv>("demo_service");
This line creates a service client that has message type mastering_ros_demo_pkg::demo_srv
and communicates to a ROS service named demo_service
:
mastering_ros_demo_pkg::demo_srv srv;
This line will create a new service object instance:
std::stringstream ss; ss << "Sending from Here"; srv.request.in = ss.str();
Fill the request instance with a string called "Sending from Here"
:
if (client.call(srv)) {
This will send the service call to the server. If it is sent successfully, it will print the response and request, if it failed, it do nothing:
ROS_INFO("From Client [%s], Server says [%s]",srv.request.in.c_str(),srv.response.out.c_str());
If the response is received, then it will print the request and the response.
After discussing the two nodes, we can discuss how to build these two nodes. The following code is added to CMakeLists.txt
to compile and build the two nodes:
add_executable(demo_service_server src/demo_service_server.cpp) add_executable(demo_service_client src/demo_service_client.cpp) add_dependencies(demo_service_server mastering_ros_demo_pkg_generate_messages_cpp) add_dependencies(demo_service_client mastering_ros_demo_pkg_generate_messages_cpp) target_link_libraries(demo_service_server ${catkin_LIBRARIES}) target_link_libraries(demo_service_client ${catkin_LIBRARIES})
We can execute the following commands to build the code:
$ cd ~/catkin_ws $ catkin_make
To start nodes, first execute roscore
and use the following commands:
$ rosrun mastering_ros_demo_pkg demo_service_server $ rosrun mastering_ros_demo_pkg demo_service_client
We can work with rosservice
using the rosservice
command:
$ rosservice list
: This will list the current ROS services$ rosservice type /demo_service
: This will print the message type of/demo_service
$ rosservice info /demo_service
: This will print the information of/demo_service
Working with ROS actionlib
In ROS services, the user implements a request/reply interaction between two nodes, but consider if the reply takes too much time or the server is not finished with the given work, we have to wait until it completes.
There is another method in ROS in which we can preempt the running request and start sending another one if the request is not finished on time as we expected. Actionlib packages provide a standard way to implement these kinds of preemptive tasks. Actionlib is highly used in robot arm navigation and mobile robot navigation. We can see how to implement an action server and action client implementation.
Like ROS services, in actionlib, we have to specify the action specification. The action specification is stored inside the action file having an extension of .action
. This file must be kept inside the action
folder, which is inside the ROS package. The action file has the following parts:
- Goal: The action client can send a goal that has to be executed by the action server. This is similar to the request in the ROS service. For example, if a robot arm joint wants to move from 45 degrees to 90 degrees, the goal here is 90 degrees.
- Feedback: When an action client sends a goal to the action server, it will start executing a call-back function. Feedback is simply giving the progress of the current operation inside the callback function. Using the feedback definition, we can get the current progress. In the preceding case, the robot arm joint has to move to 90 degrees; in this case, the feedback can be the intermediate value between 45 and 90 degrees in which the arm is moving.
- Result: After completing the goal, the action server will send a final result of completion, it can be the computational result or an acknowledgement. In the preceding example, if the joint reaches 90 degrees it achieves the goal and the result can be anything indicating it finished the goal.
We can discuss a demo action server and action client here. The demo action client will send a number as the goal. When an action server receives the goal, it will count from 0 to the goal number with a step size of 1 and with a one second delay. If it completes before the given time, it will send the result, otherwise, the task will be preempted by the client. The feedback here is the progress of counting. The action file of this task is as follows. The action file is named Demo_action.action
:
#goal definition int32 count --- #result definition int32 final_count --- #feedback int32 current_number
Here, the count value is the goal in which the server has to count from zero to this number. final_count
is the result, in which the final value after completion of a task and current_number
is the feedback value. It will specify how much the progress is.
Navigate to chapter_1_codes/mastering_ros_demo_pkg/src
and you can find the action server node as demo_action_server.cpp
and action client node as demo_action_client.cpp
.
Creating the ROS action server
In this section, we will discuss demo_action_server.cpp
. The action server receives a goal value that is a number. When the server gets this goal value, it will start counting from zero to this number. If the counting is complete, it will successfully finish the action, if it is preempted before finishing, the action server will look for another goal value.
This code is a bit lengthy, so we can discuss the important code snippet of this code.
Let's start from the header files:
#include <actionlib/server/simple_action_server.h> #include "mastering_ros_demo_pkg/Demo_actionAction.h"
The first header is the standard action library to implement an action server node. The second header is generated from the stored action files. It should include for accessing our action definition:
class Demo_actionAction {
This class contains the action server definition:
actionlib::SimpleActionServer<mastering_ros_demo_pkg::Demo_actionAction> as;
Create a simple action server instance with our custom action message type:
mastering_ros_demo_pkg::Demo_actionFeedback feedback;
Create a feedback instance for sending feedback during the operation:
mastering_ros_demo_pkg::Demo_actionResult result;
Create a result instance for sending the final result:
Demo_actionAction(std::string name) : as(nh_, name, boost::bind(&Demo_actionAction::executeCB, this, _1), false), action_name(name)
This is an action constructor, and an action server is created here by taking an argument such as Nodehandle
, action_name
, and executeCB
, where executeCB
is the action callback where all the processing is done:
as.registerPreemptCallback(boost::bind(&Demo_actionAction::preemptCB, this));
This line registers a callback when the action is preempted. The preemtCB
is the callback name executed when there is a preempt request from the action client:
void executeCB(const mastering_ros_demo_pkg::Demo_actionGoalConstPtr &goal) { if(!as.isActive() || as.isPreemptRequested()) return;
This is the callback definition which is executed when the action server receives a goal
value. It will execute callback functions only after checking whether the action server is currently active or it is preempted already:
for(progress = 0 ; progress < goal->count; progress++){ //Check for ros if(!ros::ok()){
This loop will execute until the goal value is reached. It will continuously send the current progress as feedback:
if(!as.isActive() || as.isPreemptRequested()){ return; }
Inside this loop, it will check whether the action server is active or it is preempted. If it occurs, the function will return:
if(goal->count == progress){ result.final_count = progress; as.setSucceeded(result); }
If the current value reaches the goal value, then it publishes the final result:
Demo_actionAction demo_action_obj(ros::this_node::getName());
In main()
,we create an instance of Demo_actionAction
, which will start the action server.
Creating the ROS action client
In this section, we will discuss the working of an action client. demo_action_client.cpp
is the action client node that will send the goal value consisting of a number which is the goal. The client is getting the goal value from the command line arguments. The first command line argument of the client is the goal value and the second is the time of completion for this task.
The goal value will be sent to the server and the client will wait until the given time, in seconds. After waiting, the client will check whether it completed or not; if it is not complete, the client will preempt the action.
The client code is a bit lengthy, so we will discuss the important sections of the code:
#include <actionlib/client/simple_action_client.h> #include <actionlib/client/terminal_state.h> #include "mastering_ros_demo_pkg/Demo_actionAction.h"
In action client, we need to include actionlib/client/simple_action_client.h
to get the action client APIs which are used to implement action clients:
actionlib::SimpleActionClient<mastering_ros_demo_pkg::Demo_actionAction> ac("demo_action", true);
This will create an action client instance:
ac.waitForServer();
This line will wait for an infinite time if there is no action server running on the system. It will exit only when there is an action server running on the system:
mastering_ros_demo_pkg::Demo_actionGoal goal; goal.count = atoi(argv[1]); ac.sendGoal(goal);
Create an instance of a goal and send the goal value from the first command line argument:
bool finished_before_timeout = ac.waitForResult(ros::Duration(atoi(argv[2])));
This line will wait for the result from the server until the given seconds:
ac.cancelGoal();
If it is not finished, it will preempt the action.
Building the ROS action server and client
After creating these two files in the src
folder, we have to edit the package.xml
and CMakeLists.txt
to build the nodes.
The package.xml
file should contain message generation and runtime packages as we did for ROS service and messages.
We have to include the Boost library in CMakeLists.txt
to build these nodes. Also, we have to add the action files that we wrote for this example:
find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs actionlib actionlib_msgs message_generation )
We should pass actionlib
, actionlib_msgs
, and message_generation
in find_package()
:
## System dependencies are found with CMake's conventions find_package(Boost REQUIRED COMPONENTS system)
We should add Boost
as a system dependency:
## Generate actions in the 'action' folder add_action_files( FILES Demo_action.action )
We need to add our action file in add_action_files()
:
## Generate added messages and services with any dependencies listed here generate_messages( DEPENDENCIES std_msgs actionlib_msgs )
We have to add actionlib_msgs
in generate_messages()
:
catkin_package( CATKIN_DEPENDS roscpp rospy std_msgs actionlib actionlib_msgs message_runtime ) include_directories( include ${catkin_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS} )
We have to add Boost
to include the directory:
##Building action server and action client add_executable(demo_action_server src/demo_action_server.cpp) add_executable(demo_action_client src/demo_action_client.cpp) add_dependencies(demo_action_server mastering_ros_demo_pkg_generate_messages_cpp) add_dependencies(demo_action_client mastering_ros_demo_pkg_generate_messages_cpp) target_link_libraries(demo_action_server ${catkin_LIBRARIES} ) target_link_libraries(demo_action_client ${catkin_LIBRARIES})
After catkin_make
, we can run these nodes using the following commands:
- Run
roscore
:$ roscore
- Launch the action server node:
$rosrun mastering_ros_demo_pkg demo_action_server
- Launch the action client node:
$rosrun mastering_ros_demo_pkg demo_action_client 50 4
The output of these process is shown as follows:
Creating launch files
The launch files in ROS are a very useful feature for launching more than one node. In the preceding examples, we have seen a maximum of two ROS nodes, but imagine a scenario in which we have to launch 10 or 20Ł nodes for a robot. It will be difficult if we run each node in a terminal one by one. Instead of that, we can write all nodes inside a XML based file called launch files and using a command called roslaunch, we can parse this file and launch the nodes.
The roslaunch
command will automatically start ROS Master and the parameter server. So in essence, there is no need to start the roscore
command and individual node; if we launch the file, all operations will be done in a single command.
Let's start creating launch files. Switch to the package folder and create a new launch file called demo_topic.launch
to launch two ROS nodes that are publishing and subscribing an integer value. We keep the launch files in a launch
folder, which is inside the package:
$ roscd mastering_ros_demo_pkg $ mkdir launch $ cd launch $ gedit demo_topic.launch
Paste the following content into the file:
<launch> <node name="publisher_node" pkg="mastering_ros_demo_pkg" type="demo_topic_publisher" output="screen"/> <node name="subscriber_node" pkg="mastering_ros_demo_pkg" type="demo_topic_subscriber" output="screen"/> </launch>
Let's discuss what is in the code. The <launch></launch>
tags are the root element in a launch
file. All definitions will be inside these tags.
The <node>
tag specifies the desired node to launch:
<node name="publisher_node" pkg="mastering_ros_demo_pkg" type="demo_topic_publisher" output="screen"/>
The name tag inside <node>
indicates the name of the node, pkg
is the name of the package, and type
is the name of executable we are going to launch.
After creating the launch file demo_topic.launch
, we can launch it using the following command:
$ roslaunch mastering_ros_demo_pkg demo_topic.launch
Here is the output we get if the launch is successful:
We can check the list of nodes using:
$ rosnode list
We can also view the log messages and debug the nodes using a GUI tool called rqt_console
:
$ rqt_console
We can see the logs generated by two nodes in this tool as shown here:
Applications of topics, services, and actionlib
Topics, services, and actionlib are used in different scenarios. We know topics are a unidirectional communication method, services are a bidirectional request/reply kind of communication, and actionlib is a modified form of ROS services in which we can cancel the executing process running on the server whenever required.
Here are some of areas where we use these methods:
- Topics: Robot teleoperation, publishing odometry, sending robot transform (TF), and sending robot joint states
- Services: This saves camera calibration parameters to a file, saves a map of the robot after SLAM, and loads a parameter file
- Actionlib: This is used in motion planners and ROS navigation stacks
Tip
The complete source code of this project can be cloned from the following Git repository. The following command will clone the project repo:
$ git clone https://github.com/qboticslabs/mastering_ros_demo_pkg.git
Maintaining the ROS package
Most of the ROS packages are released as open source with the BSD license. There are active developers around the globe who are contributing to the ROS platform. Maintaining packages are an important constraint in all software especially open source application. Open source software is maintained and supported by a community of developers. Creating a version control system for our package is essential if we want to maintain and accept a contribution from other developers. The preceding package is already updated in GitHub and you can view the source code of the project at https://github.com/qboticslabs/mastering_ros_demo_pkg
After uploading the code in GitHub, we can see what the procedures are to release our current package to ROS.
Releasing your ROS package
After creating a ROS package in GitHub, we can officially release our package. ROS provides detailed steps to release the ROS package using a tool called bloom (http://ros-infrastructure.github.io/bloom/). Bloom is a release automation tool, designed to make platform-specific releases from the source projects. Bloom is designed to work best with the catkin project.
The prerequisites before releasing the package are as follows:
- Install the bloom tool
- Create a Git repository for the current package
- Create an empty Git repository for the release
The following command will install bloom in Ubuntu:
$ sudo apt-get install python-bloom
Create a Git repository for the current package. The repository that has the package is called the upstream repository. Here, we already created a repository at https://github.com/qboticslabs/mastering_ros_demo_pkg.
Create an empty repository in Git for the release package. This repository is called the release
repository. We have created a package called demo_pkg-release
. This package is at https://github.com/qboticslabs/demo_pkg-release.
After meeting these prerequisites, we can start to create the release of the package. Navigate to the mastering_ros_demo_pkg
local repository where we push our package code to Git. Open a terminal inside this local repository and execute the following command:
$ catkin_generate_changelog
The purpose of this command is, it will create a CHANGELOG.rst
file inside the local repository. After executing this command it will show this option:
Continue without -all option [y/N]
. Give y
here
It will create a CHANGELOG.rst
in the local repository.
After the creation of the log file, we can update the Git repository by committing the changes:
$ git add -A $ git commit -m 'Updated CHANGELOG.rst' $ git push -u origin master
Preparing the ROS package for the release
In this step, we are checking whether the package contains change logs, versions, and so on. The following command makes our package consistent and recommended for a release.
This command should execute from the local repository of the package:
$ catkin_prepare_release
The command will set a version tag if there is no current version and commit the changes in the upstream repository.
Releasing our package
The following command starts the release. The syntax of this command is as follows:
bloom-release --rosdistro <ros_distro> --track <ros_distro> repository_name $ bloom-release --rosdistro indigo --track indigo mastering_ros_demo_pkg
When this command is executed, it will go to the rosdistro
(https://github.com/ros/rosdistro) package repository to get the package details. The rosdistro
package in ROS contains an index file, which contains a list of all the packages in ROS. Currently, there is no index for our package because this is our first release, but we can add our package details to this index file called distributions.yaml
.
The following message will be displayed when there is no reference of the package in rosdistro
:
We should give the release repository in the terminal that is marked in red in the preceding screenshot. In this case, the URL was https://github.com/qboticslabs/demo_pkg-release.
In the upcoming steps, the wizard will ask for the repository name, upstream, URL, and so on. We can give these options and finally, a pull request to rosdistro
will be submitted, which is shown in the following screenshot:
The pull
request for this package can be viewed at https://github.com/ros/rosdistro/pull/9662.
If it is accepted, it will merge to indigo/distribution.yaml
, which contains the index of all packages in ROS.
The following screenshot displays the package as an index in indigo/distribution.yaml
:
After this step, we can confirm that the package is released and officially added to the ROS index.
Creating a Wiki page for your ROS package
ROS wiki allows users to create their own home pages to showcase their package, robot, or sensors. The official wiki page of ROS is wiki.ros.org. Now, we are going to create a wiki page for our package.
Tip
Downloading the example code
You can download the example code files from your account at http://www.packtpub.com for all the Packt Publishing books you have purchased. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you. You can also download chapter codes from https://github.com/qboticslabs/mastering_ros.git.
The first step is to register in wiki using your mail address. Go to wiki.ros.org, and click on the login button as shown in the screenshot:
After clicking on Login, you can register or directly login with your details if you are already registered. After login, press the user name link on the right side of the wiki page as shown in the screenshot:
After clicking on this link, you will get a chance to create a home page for your package; you will get a text editor with GUI to enter data into. The following screenshot shows you the page we created for this demo package:
The wiki page of this package can be viewed at http://wiki.ros.org/qboticslabs.