Table of Contents
Introduction
When developing projects in robotics, the need for additional features often arises, such as advanced motion planning or sensor fusion techniques. Implementing these functionalities from scratch can be time-consuming. In such cases, leveraging existing libraries that offer these features can significantly accelerate development. For example, in the domain of mobile robotics, utilizing libraries for path planning or simultaneous localization and mapping (SLAM) can greatly enhance the capabilities of autonomous robots. This article aims to clarify concepts around incorporating external libraries into ROS 2 C++ packages, facilitating faster development and expanding the capabilities of robotic applications.
Starting Point
No understanding of
- ROS2 C++ libraries and components
- ROS2 C++ Package Structure
- Creating custom libraries
Learning outcomes
Easily able to
- Creating Custom Modules into ROS2 Packages
- Install External Libraries to your ROS2 Packages
- Concept clearning of Python Package, Library and module
Understanding C++ Terminologies
You can find our very useful open source C++-based libraries, but to utilize them in your projects, you need to understand that C++ requires the step of getting compiled to run it. For that, we need to have a system like CmakeLists.txt to compile and produce an executable that we can run. Lets first understand difference between a library and simple code inclusion.
C++ Included Code
Including a C++ source file (#include "my_functions.cpp"
) directly into another file is more like copying and pasting the contents of that file. It’s not a standard way to distribute reusable code and this is not a library.
// my_functions.cpp
#include <iostream>
void sayHello() {
std::cout << "Hello, Robotisim!" << std::endl;
}
// main.cpp
#include "my_functions.cpp"
int main() {
sayHello();
return 0;
}
![c++ programming](https://robotisim.com/wp-content/uploads/2024/05/homepage_programming.png)
C++ Libraries
A library is a precompiled set of functions and code that can be reused by other programs. It is typically distributed as a binary file (libexample.a
for static libraries or libexample.so
for dynamic/shared libraries) that can be linked to an executable.
Lets understand through a popular example
- Header only library but no compiled .a or .so file
- In this example,
#include <iostream>
includes theiostream
header file, which provides functionality for input and output operations
- In this example,
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
Creating Custom C++ Library
Lets look at an example of static library creation , to build our understanding about the process and get rid of copy pasting of source codes.
Header File
#ifndef BOT_MOVE_H
#define BOT_MOVE_H
enum DIR { FORWARD, BACKWARD};
bool botMove(DIR direction);
void botStop();
#endif
Source File
#include "bot_move.h"
bool botMove(Dir direction)
{std::cout << "Moving robot";
return true;}
void botStop() {
std::cout << "Stop";}
Compile Library
g++ -c bot_move.cpp
ar rcs libbot_move.a bot_move.o
Library Files Understanding
After compiling, you will obtain a static library file libbot_move.a
that contains the compiled code for the botMove
and botStop
functions.
This library can be linked to other programs that need to control a robot’s movement.
Utilising our custom Library
Once these library files are created then you can utilize them or link them into your C++ ROS2 Code
Main Code
#include "bot_move.h"
int main() {
moveRobot(FORWARD); // Move the robot forward at speed 50
stopRobot(); // Stop the robot
return 0;
}
This code is inlcuding the header file but when we will compile we will mention that do not take definations of header files from .cpp instead take from .a compile library
Linking Command
g++ main.cpp -L. -lbot_move -o robot_movement
Explanation
main.cpp
is your main C++ file that uses the functions from thebot_move
library.-L.
tells the compiler to look for libraries in the current directory.-lbot_move
specifies the name of the library to link (libbot_move.a
).-o robot_movement
specifies the output executable name.
ROS2 C++ Package Strucutre
When we create a python package using ROS2 command, we have couple of things to understand
ros2 pkg create
: This is the command used to create a new ROS 2 package.--build-type ament_cmake
: This option specifies the build system to use for the package. In this case,ament_cmake
is selected.--node-name sensor_fusion_node
: This option specifies that a node namedsensor_fusion_pkg
: This is the name of the package that will be created.
ros2 pkg create --build-type ament_cmake --node-name sensor_fusion_node sensor_fusion_pkg
When we execute this command, we see a package with a node automatically created with a fixed structure. This below structure is produced mainly for keeping standards fixed for compatibility between tools and ease of maintenance. Lets look at its structure and important files
Node
sensor_fusion_pkg/
├── include/
│ └── sensor_fusion_pkg/
│ └── sensor_fusion_node.hpp
├── src/
│ ├── sensor_fusion_node.cpp
│ └── CMakeLists.txt
├── CMakeLists.txt
├── package.xml
└── README.md
Node
#include <rclcpp>
#include libraries
class Node{
publishers.create();
subscriber.create();
}
void main (){
Node.spin();
}
CmakeLists.txt
include_directories(include)
add_executable(sensor_fusion_node src/sensor_fusion_node.cpp)
ament_target_dependencies(sensor_fusion_node rclcpp)
Including OpenCV and Eigen Library into ROS2 C++ Package
Lets bring in some popular libraries to be included into our ROS2 C++ node afterinstallation , just to understand the process.
sensor_fusion_node.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
#include <opencv2/core.hpp>
#include <Eigen/Dense>
class SensorFusion : public rclcpp::Node
{
public:
SensorFusion()
: Node("sensor_fusion_node")
{
// Use OpenCV
cv::Mat image(100, 100, CV_8UC3, cv::Scalar(0, 255, 0));
cv::imshow("Image", image);
cv::waitKey(0);
// Use Eigen
Eigen::MatrixXd matrix(3, 3);
matrix << 1, 2, 3, 4, 5, 6, 7, 8, 9;
std::cout << "Eigen Matrix:\n" << matrix << std::endl;
}
};
int main(int argc, char *argv[])
{
rclcpp::init(argc, argv);
auto node = std::make_shared<SensorFusion>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
CmakeList.txt
cmake_minimum_required(VERSION 3.5)
project(sensor_fusion_pkg)
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
# Find OpenCV package
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
# Find Eigen package
find_package(Eigen3 REQUIRED)
include_directories(${EIGEN3_INCLUDE_DIR})
# Find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
# Create an executable
add_executable(SensorFusion_node src/SensorFusion.cpp)
# Link the OpenCV and Eigen libraries
target_link_libraries(SensorFusion_node ${OpenCV_LIBRARIES} ${EIGEN3_LIBRARIES})
ament_target_dependencies(my_node rclcpp std_msgs)
# Install the executable
install(TARGETS my_node
DESTINATION lib/${PROJECT_NAME})
ament_package()
Utilising Custom Library in ROS2 C++ Package
- Includes necessary headers:
rclcpp
for ROS 2 functionality,std_msgs/msg/string.hpp
for standard message types, andbot_move.h
for your custom library. - Defines a class
BotMove
that inherits fromrclcpp::Node
, providing a constructor that initializes the node with the name “bot_move”. - Inside the constructor, it calls a function (
moveRobot()
) from your custom librarybot_move
and logs the result usingRCLCPP_INFO
.
sensor_fusion_node.cpp
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
#include "bot_move.h"
class BotMove : public rclcpp::Node
{
public:
BotMove()
: Node("bot_move")
{
int result = moveRobot();
RCLCPP_INFO(this->get_logger(), "Result: %d", result);
}
};
int main(int argc, char *argv[])
{
rclcpp::init(argc, argv);
auto node = std::make_shared<BotMove>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
CmakeLists
cmake_minimum_required(VERSION 3.5)
project(sensor_fusion_pkg)
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
include_directories(
include
${CMAKE_CURRENT_SOURCE_DIR}/lib
)
# create an executable
add_executable(bot_move_node src/bot_move.cpp)
# link your custom library
target_link_libraries(bot_move_node ${CMAKE_CURRENT_SOURCE_DIR}/lib/libmove_bot.a)
ament_target_dependencies(sensor_fusion_node rclcpp)
# install the executable
install(TARGETS bot_move_node
DESTINATION lib/${PROJECT_NAME})
ament_package()
- Includes directories for the headers of your custom library (
${CMAKE_CURRENT_SOURCE_DIR}/lib
).
- Creates an executable named
BotMove
frombot_move.cpp
and links it with your custom library (target_link_libraries
). - Specifies the dependencies (
ament_target_dependencies
).
FAQ
Q: What is the standard structure of a ROS 2 C++ package?
A: A standard ROS 2 C++ package structure includes directories for include files, source files, a CMakeLists.txt
file, a package.xml
file, and other necessary files for building and running the package.
Q: How do you create a ROS 2 C++ package?
A: You can create a ROS 2 C++ package using the command:
bashCopy coderos2 pkg create --build-type ament_cmake --node-name sensor_fusion_node sensor_fusion_pkg
Q: How do you include OpenCV and Eigen libraries in a ROS 2 C++ package?
A: To include OpenCV and Eigen libraries, you need to find these packages and include their directories in your CMakeLists.txt
file:
cmakeCopy codefind_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
find_package(Eigen3 REQUIRED)
include_directories(${EIGEN3_INCLUDE_DIR})
Q: How do you use a custom library in a ROS 2 C++ package?
A: You include the header of your custom library in your node’s source file and link the compiled library in your CMakeLists.txt
file. For example, include the header in your .cpp
file:
#include "bot_move.h"
And link the library in CMakeLists.txt
:
cmakeCopy codetarget_link_libraries(bot_move_node ${CMAKE_CURRENT_SOURCE_DIR}/lib/libmove_bot.a)
Q: Why is EIGEN3 not included in the target_link_libraries
?
A: Eigen is a header-only library, which means its functionality is included directly through headers, and there are no compiled binaries to link against.
Q: Why is OpenCV written as OpenCV_LIBS
while Boost is written as Boost_LIBRARIES
?
A: The naming conventions for linking libraries in CMakeLists.txt
can differ based on how the CMake configuration files for those libraries are set up. OpenCV_LIBS
and Boost_LIBRARIES
are the variables defined in their respective CMake configurations to hold the library linking information.