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.
Enjoy Reading This Article?
Here are some more articles you might like to read next: