Part2: Developing a ROS C++ package
A closer look at CMakeLists.txt and package.xml
We looked at creating a catkin package in Part1. A catkin package is characterised by a CMakeLists.txt and a package.xml. Lets take a deeper look into the important parts of these. Only the main parts of CMakeLists.txt is shown below and this is in no way a complete tutorial to CMake. By default, catkin_create_pkg generates a long CMakeLists.txt with lots of comments for users to read and understand. The external settings that are used to create the package are listed under the find_package() tag. An excellent explanation to what catkin_package() is provided in here.
cmake_minimum_required(VERSION 3.0.2)
project(my_test_pkg)
add_compile_options(-std=c++11)
find_package(catkin REQUIRED COMPONENTS
moveit_core
roscpp
std_msgs
)
catkin_package(
INCLUDE_DIRS include
# LIBRARIES my_test_pkg
CATKIN_DEPENDS moveit_core roscpp std_msgs
# 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++ executable
add_executable(${PROJECT_NAME} src/my_test_pkg.cpp)
## Add cmake target dependencies of the executable
add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
## Specify libraries to link a library or executable target against
target_link_libraries(
${PROJECT_NAME}
${catkin_LIBRARIES}
)
#############
## Testing ##
#############
## Add gtest based cpp test target and link libraries
if(CATKIN_ENABLE_TESTING)
find_package(rostest REQUIRED)
add_rostest_gtest(
test_one
test/test_one.test
test/test_one.cpp
)
target_link_libraries(
test_one
${catkin_LIBRARIES}
)
add_dependencies(
test_one
${catkin_EXPORTED_TARGETS}
)
catkin_add_gtest(
test_two
test/test_two.cpp
src/hello.cpp
src/add.cpp
)
target_link_libraries(
test_two
${catkin_LIBRARIES}
)
The include_directories() contain the header files of the packages that you use in your project. If the project uses OpenCV as well, then this has to be added as
include_directories(
include
${catkin_INCLUDE_DIRS}
${OpenCV_INCLUDE_DIRS}
)
Lets say we want to create two header files like hello.h and add.h, it would be a good practice to organize it as follows.
my_test_pkg
|_ CMakeLists.txt
|_ package.xml
|_ src/
|_ include/
|_ my_test_pkg/
|_ greet/
|_ hello.h
|_ math/
|_ add.h
The header hello.h would be something like
#ifndef _HELLO_H_
#define _HELLO_H_
#include<iostream>
#include<string>
namespace greeting
{
class Hello
{
public:
Hello(); // constructor
~Hello(); // destructor
void printHello(const std::string & str):
protected:
// protected attributes
private:
// private attributes
};
}
#endif _HELLO_H_
Then similarly in the src, create two folders greet and math with the corresponding implementations (for example hello.cpp and add.cpp). The headers can then be used as #include <my_test_pkg/greet/hello.h> and #include <my_test_pkg/math/add.h>.
The hello.cpp would be like
#include <my_test_pkg/greet/hello.h>
using namespace greeting;
Hello::Hello()
{
std::cout<<"Hello Constructor Invoked"<< std::endl;
}
Hello::~Hello()
{
std::cout<<"Hello Destructor Invoked"<< std::endl;
}
void Hello::printHello(const std::string & str)
{
std::cout<<" The string to print is " << str << std::endl;
}
As you know that a C++ program allows only one main() function, we could take a look at how this is organised in the src directory.
my_test_pkg
|_ CMakeLists.txt
|_ package.xml
|_ include/
|_ src/
|_ greet/
|_ hello.cpp
|_ math/
|_ add.cpp
|_ main.cpp
The main.cpp will look something like
#include <my_test_pkg/greet/hello.h>
#include<my_test_pkg/math/add.h>
#include <ros/ros.h>
int main(int argc, char **argv)
{
ros::init(argc, argv, "my_test_node"); // initiating a ros node
ros::AsyncSpinner spinner(1);
spinner.start();
ros::Rate loop_rate(5);
int N[100] = {1, 2, 3 ....};
std::string myName = "JonDoe";
while (ros::ok())
{
// call your functions: as an example
printHello(myName);
addNumbers(N)
loop_rate.sleep();
}
spinner.stop();
return 0;
// // Wait for ROS threads to terminate
// ros::waitForShutdown();
}
A little bit of ROS specific stuffs are involved but it should be quite straight forward.
The add_executable() is where the whole executables are listed. In this particular case, it would be
`add_executable(
src/hello.cpp
src/add.cpp
src/main.cpp
)`
As the name goes, the add_dependencies() help in adding the dependencies which is an essential part of your package. And finally the libraries are to be linked to your executable which is done using the
target_link_libraries(
${PROJECT_NAME}
${OpenCV_LIBS}
${catkin_LIBRARIES}
)
The package.xml should have the build_depend and exec_depend tags for all those listed under the find_package(). For a deeper understanding, have a look at here.
In no way, this article is a complete with respect to learning ROS CPP but I hope this would give a good enough start point. We will look at adding gtest() and rostest() in the next article.