This tutorial is created by Robotics Ambassador 023 Enzo
Rosbotics Ambassador Program https://www.theconstruct.ai/rosbotics-ambassador/)
Cours: ROS2 Basics in 5 Days C++: https://app.theconstructsim.com/courses/133
Introduction
Le but de ce tutoriel est d’apprendre à créer et utiliser des bibliothèques dans les packages ROS2 C++. Lors du développement d’un projet, il est essentiel de garder son code structuré pour assurer un développement efficace. L’utilisation de bibliothèque (ou library) est indispensable pour cela car cela permet de regrouper les codes sources en fonction de leurs usages et donc de séparer des fonctionnalités distinctes. La création de bibliothèques est donc essentielle pour rendre votre code plus structuré, modulaire et réutilisable. Nous allons donc voir ensemble comment déclarer et faire appel une library dans les packages ROS2 C++. Nous allons dans un premier temps voir comment simplement utiliser un header dans un package ROS2 puis nous déclarerons la même bibliothèque dans un package indépendant et utiliserons celle ci dans un autre package.
Pré-requis
Pour lancer et tester le code, vous devez lancer la simulation du turtlebot3 sur gazebo. Pour faire cela, entez les commandes suivantes dans le terminal:
# Declare le model de turtlebot3 à simuler
export TURTLEBOT3_MODEL=waffle_pi
# Lance la simulation du turtlebot dans gazebo
ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py
Une fois la simulation du turtlebot3 lancée, celle-ci va publier sur le topic /odom que nous allons utiliser dans le code exemple. Après avoir exécuté ces commandes, appuyez sur le bouton de l’interface gazebo et selectionnez Open Gazebo.
Vous devriez obtenir le résultat suivant:
Si vous voulez effectuer le tutoriel sur votre installation local, vous devez avoir ROS2 installé (https://docs.ros.org/en/humble/Installation.html), avoir configuré un workspace (https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Creating-A-Workspace/Creating-A-Workspace.html) et avoir installé le turtlebot3.
# pour installer tous les packages du turtlebot3
# remplacez humble par votre distro ROS2
sudo apt-get install ros-humble-turtlebot3*
# n'oubliez pas d'exporter le modèle de Turtlebot3 que vous souhaitez utiliser
export TURTLEBOT3_MODEL=waffle_pi
Méthode 1: Utiliser un header dans un package ROS2 C++
Nous allons commencer par créer le package C++ qui va contenir le code et le header:
cd ~/ros2_ws/src
ros2 pkg create cpp_header_package --build-type ament_cmake --dependencies rclcpp nav_msgs geometry_msgs
On va créer le fichier header odometry_toolbox.hpp dans le dossier include/cpp_header_package qui va contenir notre class avec les prototypes des méthodes que nous allons importer dans le node:
cd ~/ros2_ws/src/cpp_header_package/include/cpp_header_package touch odometry_toolbox.hpp
Affichez l’éditeur de code et ouvrez le fichier odometry_toolbox.hpp. Ajouter ensuite le code suivant:
// odometry_toolbox.hpp
#ifndef ODOMETRY_TOOLBOX_HPP
#define ODOMETRY_TOOLBOX_HPP
#include "geometry_msgs/msg/point.hpp"
#include "nav_msgs/msg/odometry.hpp"
class OdometryToolbox {
public:
// Static method
static void
point_from_odom_offset_init(nav_msgs::msg::Odometry actual_odom,
nav_msgs::msg::Odometry init_odom,
geometry_msgs::msg::Point &output_point);
private:
// Private constructor to make the class uninstantiable
OdometryToolbox();
};
#endif // ODOMETRY_TOOLBOX_HPP
On crée ensuite le fichier source odometry_toolbox.cpp dans le folder src qui va contenir la déclaration des méthodes de la class OdometryToolbox.
cd ~/ros2_ws/src/cpp_header_package/src
touch odometry_toolbox.cpp
Affichez l’éditeur de code et ouvrez le fichier odometry_toolbox.cpp. Ajouter ensuite le code suivant:
// odometry_toolbox.cpp
#include "cpp_header_package/odometry_toolbox.hpp"
#include "geometry_msgs/msg/detail/point__struct.hpp"
/*
This function substacts the actual odom value of the robot with it's initial to
*/
void OdometryToolbox::point_from_odom_offset_init(
nav_msgs::msg::Odometry actual_odom, nav_msgs::msg::Odometry init_odom,
geometry_msgs::msg::Point &output_point) {
// geometry_msgs::msg::Point point_with_offset;
output_point.x =
actual_odom.pose.pose.position.x - init_odom.pose.pose.position.x;
output_point.y =
actual_odom.pose.pose.position.y - init_odom.pose.pose.position.y;
output_point.z =
actual_odom.pose.pose.position.z - init_odom.pose.pose.position.z;
}
// Private constructor definition
OdometryToolbox::OdometryToolbox() {
// This constructor is private, so it cannot be called from outside the class
}
Ce code d’exemple déclare une class contenant une méthode permettant de soustraire terme à terme la position d’une odom avec une autre et de retourner le point obtenu. Cela va être utilisé pour appliquée une offset sur l’odometry avec la position initiale lors du lancement du node, la position publiée sur /point_with_offset commencera à (0.0, 0.0, 0.0).
On va ensuite créer le ROS node c++ qui va importer la class OdometryToolbox et va l’utiliser dans le callback du topic /odom.
cd ~/ros2_ws/src/cpp_header_package/src touch main.cpp
Ouvrez le fichier main.cpp et ajouter le code suivant:
// main.cpp
#include "cpp_header_package/odometry_toolbox.hpp"
#include "nav_msgs/msg/odometry.hpp"
#include "geometry_msgs/msg/point.hpp"
#include "rclcpp/rclcpp.hpp"
#include <iostream>
class OdomRepublisherNode : public rclcpp::Node {
public:
OdomRepublisherNode() : Node("odom_republisher") {
// Create a subscriber to the odometry topic
odom_subscription_ = this->create_subscription<nav_msgs::msg::Odometry>(
"odom", 10,
std::bind(&OdomRepublisherNode::odomCallback, this,
std::placeholders::_1));
// Create a publisher to republish the odometry
point_publisher_ = this->create_publisher<geometry_msgs::msg::Point>(
"point_with_offset", 10);
is_odom_init = false;
}
private:
void odomCallback(const nav_msgs::msg::Odometry::SharedPtr msg) {
geometry_msgs::msg::Point point_with_offset;
// Use the odom message as input and substract it with its initial value
// at node start and republish it
if (is_odom_init == false) {
init_odom = *msg;
is_odom_init = true;
}
// Call the function from the imported header
OdometryToolbox::point_from_odom_offset_init(*msg, init_odom,
point_with_offset);
// Republish the received odometry message
point_publisher_->publish(point_with_offset);
}
rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odom_subscription_;
rclcpp::Publisher<geometry_msgs::msg::Point>::SharedPtr point_publisher_;
bool is_odom_init;
nav_msgs::msg::Odometry init_odom;
};
int main(int argc, char **argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<OdomRepublisherNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Une fois que tous les fichiers sont créés, votre package devrait ressembler à cela:
Vous devez ensuite mettre à jour le CMakeLists.txt pour créer l’executable et le lié la bibliothèque créée:
cmake_minimum_required(VERSION 3.8)
project(cpp_header_package)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
# On laisse le compilateur chercher les headers dans le dossier include
include_directories(include)
# Créer l'executable en indiquant le fichier source de la bibliothèque
add_executable(my_node src/main.cpp src/odometry_toolbox.cpp)
# Indique le lien entre l'executable et les libraries requises
ament_target_dependencies(my_node rclcpp nav_msgs geometry_msgs geometry_msgs)
# Installe l'executable
install(TARGETS
my_node
DESTINATION lib/${PROJECT_NAME})
# Install les fichiers header
install(DIRECTORY
include/
DESTINATION include/${PROJECT_NAME})
# Install other files
install(FILES
package.xml
DESTINATION share/${PROJECT_NAME})
ament_package()
Vous pouvez compiler le package pour tester que tout fonctionne:
cd ~/ros2_ws
colcon build
source install/setup.bash
Methode 2: Utiliser un package indépendant comme bibliothèque
Part 1: Créer un package qui va contenir le source code de la library
Nous allons commencer par créer le package C++ qui va servir de bibliothèque. Ouvrez un terminal et exécutez les commandes suivantes:
cd ~/ros2_ws/src
ros2 pkg create cpp_point_library --build-type ament_cmake --dependencies rclcpp nav_msgs geometry_msgs
Maintenant que le package est initialisé, nous allons créer le fichier qui va contenir le source code de la library. Pour faire cela, ouvrez un terminal et exécutez les commandes suivantes:
cd ~/ros2_ws/src/cpp_point_library/include/cpp_point_library touch odometry_toolbox.hpp
Affichez l’éditeur de code et ouvez le fichier odometry_toolbox.hpp. Ajouter ensuite le code suivant:
//odometry_toolbox.hpp
#ifndef ODOMETRY_TOOLBOX_HPP
#define ODOMETRY_TOOLBOX_HPP
#include "geometry_msgs/msg/point.hpp"
#include "nav_msgs/msg/odometry.hpp"
class OdometryToolbox {
public:
// Static method example
static void
point_from_odom_offset_init(nav_msgs::msg::Odometry actual_odom,
nav_msgs::msg::Odometry init_odom,
geometry_msgs::msg::Point &output_point);
private:
// Private constructor to make the class uninstantiable
OdometryToolbox();
};
#endif // ODOMETRY_TOOLBOX_HPP
Créez ensuite le ficher odometry_toolbox.cpp avec les commandes suivante
cd ~/ros2_ws/src/cpp_point_library/src touch odometry_toolbox.cpp
Ajoutez y ensuite le code ci dessous:
//odometry_toolbox.cpp
#include "cpp_point_library/odometry_toolbox.hpp"
#include "geometry_msgs/msg/detail/point__struct.hpp"
/*
This function substacts the actual odom value of the robot with it's initial to
*/
void OdometryToolbox::point_from_odom_offset_init(
nav_msgs::msg::Odometry actual_odom, nav_msgs::msg::Odometry init_odom,
geometry_msgs::msg::Point &output_point) {
// geometry_msgs::msg::Point point_with_offset;
output_point.x = actual_odom.pose.pose.position.x - init_odom.pose.pose.position.x;
output_point.y = actual_odom.pose.pose.position.y - init_odom.pose.pose.position.y;
output_point.z = actual_odom.pose.pose.position.z - init_odom.pose.pose.position.z;
// return *output_point;
}
// Private constructor definition
OdometryToolbox::OdometryToolbox() {
// This constructor is private, so it cannot be called from outside the class
}
Les codes de la library sont similaires à ceux de la partie precedente à l’exeception du nom du package utilisé dans l’import de odometry_toolbox.cpp et l’abscence de main.cpp vu qu’il sera déclarer dans le package qui utilisera la library. Il faut également modifier le CMakeLists.txt:
cmake_minimum_required(VERSION 3.8)
project(cpp_point_library)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
# On laisse le compilateur chercher les headers dans le dossier include
include_directories(include)
# definit la target pour identifier la biblothèque comme étant cpp_point_library
# et indique le fichier source
add_library(cpp_point_library src/odometry_toolbox.cpp)
# Indique les libraries dont dépend notre library
ament_target_dependencies(cpp_point_library rclcpp nav_msgs geometry_msgs)
# Exporte la library
ament_export_targets(cpp_point_library HAS_LIBRARY_TARGET)
# installe le dossier include/cpp_point_library à install/include/cpp_point_library
install(
DIRECTORY include/cpp_point_library
DESTINATION include
)
# Ces lignes permettent de rendre la library importables dans d'autres
# projets cmake et indiquent les emplacements standards
# où sauvegarder la library dans le workspace
install(
TARGETS cpp_point_library
EXPORT cpp_point_library
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
RUNTIME DESTINATION bin
INCLUDES DESTINATION include
)
ament_package()
Vous pouvez compiler le package pour tester que tout fonctionne:
cd ~/ros2_ws colcon build source install/setup.bash
Part 2: Créer un package qui va utiliser la library créée précédemment
Nous allons créer un autre package C++ qui va utiliser la bibliothèque créée dans la partie 1. Ouvrez un terminal et exécutez les commandes suivantes:
cd ~/ros2_ws/src ros2 pkg create cpp_point_package --build-type ament_cmake --dependencies rclcpp std_msgs nav_msgs geometry_msgs cpp_point_library
Lors de la création du package, on a indiquer les dépendances en ajoutant également le package de notre library. Une fois le package créer, nous allons créer un fichier main.cpp qui contiendra le node exemple.
cd ~/ros2_ws/src/cpp_point_package/src touch main.cpp
Collez ensuite le code ci-dessous dans le fichier main.cpp. Encore une fois le node va avoir le même contenu que celui de la méthode 1 à l’exception de l’import de cpp_point_library/odometry_toolbox.hpp pour faire appel à la library.
// main.cpp
#include "cpp_point_library/odometry_toolbox.hpp"
#include "nav_msgs/msg/odometry.hpp"
#include "geometry_msgs/msg/point.hpp"
#include "rclcpp/rclcpp.hpp"
#include <iostream>
class OdomRepublisherNode : public rclcpp::Node {
public:
OdomRepublisherNode() : Node("odom_republisher") {
// Create a subscriber to the odometry topic
odom_subscription_ = this->create_subscription<nav_msgs::msg::Odometry>(
"odom", 10,
std::bind(&OdomRepublisherNode::odomCallback, this,
std::placeholders::_1));
// Create a publisher to republish the odometry
point_publisher_ = this->create_publisher<geometry_msgs::msg::Point>(
"point_with_offset", 10);
is_odom_init = false;
}
private:
void odomCallback(const nav_msgs::msg::Odometry::SharedPtr msg) {
geometry_msgs::msg::Point point_with_offset;
// Use the odom message as input and substract it with its initial value
// at node start and republish it
if (is_odom_init == false) {
init_odom = *msg;
is_odom_init = true;
}
// Call the function from the imported header
OdometryToolbox::point_from_odom_offset_init(*msg, init_odom,
point_with_offset);
// Republish the received odometry message
point_publisher_->publish(point_with_offset);
}
rclcpp::Subscription<nav_msgs::msg::Odometry>::SharedPtr odom_subscription_;
rclcpp::Publisher<geometry_msgs::msg::Point>::SharedPtr point_publisher_;
bool is_odom_init;
nav_msgs::msg::Odometry init_odom;
};
int main(int argc, char **argv) {
rclcpp::init(argc, argv);
auto node = std::make_shared<OdomRepublisherNode>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
Pour pouvoir utiliser votre library dans votre package, vous devez mettre à jour le CMakeLists.txt du package.
//CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(cpp_point_package)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(nav_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
# verifier que l'on recherche bien notre library et qu'elle est required
find_package(cpp_point_library REQUIRED)
# Crée l'executable
add_executable(odom_to_point_node src/main.cpp)
# Lie l'executable avec les libraries requises sans oublier cpp_point_library
ament_target_dependencies(odom_to_point_node rclcpp nav_msgs geometry_msgs geometry_msgs cpp_point_library)
# Installe l'executable
install(TARGETS
odom_to_point_node
DESTINATION lib/${PROJECT_NAME})
ament_package()
Une fois que toutes les manipulations effectuées, vous pouvez build le package.
cd ~/ros2_ws
colcon build
source install/setup.bash
Step 3: Tester le node
Pour lancer le node, lancez la simulation comme indiqué dans les prérequis. Vous devez ensuite exécuter la commande suivante dans un terminal:
# Ne lancez pas les deux nodes en même temps car ils publient sur le même topic
# pour la methode 1
ros2 run cpp_header_package my_node
# pour la méthode 2
ros2 run cpp_point_package odom_to_point_node
Après avoir lancé l’execution du code, vous pouvez echo le topic /point_with_offset pour afficher les points publiés:
ros2 topic echo /point_with_offset
Vous pouvez utiliser l’instruction suivante pour déplacer le robot et tester l’offset au lancement du node:
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.1, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"
Merci d’avoir suivi ce poste.
Ressources
• ROS Ignite Academy
• ROS2 Full course for beginner
• Rosject utilisé pour le poste
Feedback
Cet article vous a plu ? Avez-vous des questions sur ce qui est expliqué ? Quoi qu’il en soit, n’hésitez pas à laisser un commentaire dans la section des commentaires ci-dessous, afin que nous puissions interagir et apprendre les uns des autres.
Si vous souhaitez en savoir plus sur d’autres sujets liés à ROS, faites-le nous savoir dans l’espace commentaires et nous ferons une vidéo ou un article à ce sujet.
Topics: ROS Q&A | ros2
Comment
zUGrEsjE
Comment