Créer et utiliser des plugins (C++)
Objectif : Apprendre à créer et à charger un plugin simple à l’aide de pluginlib
Niveau du tutoriel : Débutant
Durée : 20 minutes
Contenu
Arrière-plan
Ce tutoriel est dérivé de http://wiki.ros.org/pluginlib et Writing and Using a Simple Plugin Tutorial.
pluginlib est une bibliothèque C++ pour charger et décharger des plugins à partir d’un package ROS. Les plugins sont des classes chargeables dynamiquement qui sont chargées à partir d’une bibliothèque d’exécution (c’est-à-dire un objet partagé, une bibliothèque liée dynamiquement). Avec pluginlib, il n’est pas nécessaire de lier explicitement leur application à la bibliothèque contenant les classes - à la place, pluginlib peut ouvrir une bibliothèque contenant des classes exportées à tout moment sans que l’application ait connaissance préalable de la bibliothèque ou du fichier d’en-tête contenant la définition de classe . Les plugins sont utiles pour étendre/modifier le comportement de l’application sans avoir besoin du code source de l’application.
Conditions préalables
Ce tutoriel suppose des connaissances de base en C++ et que vous avez installé pluginlib
.
sudo apt-get install ros-rolling-pluginlib
Tâches
Dans ce didacticiel, vous allez créer deux nouveaux packages, l’un définissant la classe de base et l’autre fournissant les plugins. La classe de base définira une classe de polygone générique, puis nos plugins définiront des formes spécifiques.
1 Créer le package de classe de base
Créez un nouveau package vide dans votre dossier ros2_ws/src
avec la commande de terminal suivante.
ros2 pkg create --build-type ament_cmake polygon_base --dependencies pluginlib --node-name area_node
Ouvrez votre éditeur préféré, éditez ros2_ws/src/polygon_base/include/polygon_base/regular_polygon.hpp
et collez-y ce qui suit :
#ifndef POLYGON_BASE_REGULAR_POLYGON_HPP
#define POLYGON_BASE_REGULAR_POLYGON_HPP
namespace polygon_base
{
class RegularPolygon
{
public:
virtual void initialize(double side_length) = 0;
virtual double area() = 0;
virtual ~RegularPolygon(){}
protected:
RegularPolygon(){}
};
} // namespace polygon_base
#endif // POLYGON_BASE_REGULAR_POLYGON_HPP
Ce code ci-dessus devrait être assez explicite… nous créons une classe abstraite appelée RegularPolygon
. Une chose à noter est la présence de la méthode initialize. Avec pluginlib
, un constructeur sans paramètres est requis pour les classes donc, si des paramètres sont requis, nous utilisons la méthode initialize pour initialiser l’objet.
Nous devons rendre cet en-tête disponible pour les autres classes, alors ouvrez ros2_ws/src/polygon_base/CMakeLists.txt
pour l’éditer. Ajoutez les lignes suivantes après la commande ament_target_dependencies
.
install(
DIRECTORY include/
DESTINATION include
)
Et ajoutez cette commande avant la commande ament_package
ament_export_include_directories(
include
)
Nous reviendrons sur ce package plus tard pour écrire notre nœud de test.
2 Créer le package de plug-in
Nous allons maintenant écrire deux implémentations non virtuelles de notre classe abstraite. Créez un deuxième package vide dans votre dossier ros2_ws/src
avec la commande de terminal suivante.
ros2 pkg create --build-type ament_cmake polygon_plugins --dependencies polygon_base pluginlib --library-name polygon_plugins
2.1 Code source des plugins
Ouvrez ros2_ws/src/polygon_plugins/src/polygon_plugins.cpp
pour le modifier et collez-y ce qui suit :
#include <polygon_base/regular_polygon.hpp>
#include <cmath>
namespace polygon_plugins
{
class Square : public polygon_base::RegularPolygon
{
public:
void initialize(double side_length) override
{
side_length_ = side_length;
}
double area() override
{
return side_length_ * side_length_;
}
protected:
double side_length_;
};
class Triangle : public polygon_base::RegularPolygon
{
public:
void initialize(double side_length) override
{
side_length_ = side_length;
}
double area() override
{
return 0.5 * side_length_ * getHeight();
}
double getHeight()
{
return sqrt((side_length_ * side_length_) - ((side_length_ / 2) * (side_length_ / 2)));
}
protected:
double side_length_;
};
}
#include <pluginlib/class_list_macros.hpp>
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)
L’implémentation des classes Square et Triangle devrait être assez simple : enregistrez la longueur du côté et utilisez-la pour calculer l’aire. Le seul élément spécifique à pluginlib est les trois dernières lignes, qui invoquent des macros magiques qui enregistrent les classes en tant que plugins réels. Passons en revue les arguments de la macro PLUGINLIB_EXPORT_CLASS
:
Le type complet de la classe de plugin, dans ce cas,
polygon_plugins::Square
.Le type complet de la classe de base, dans ce cas,
polygon_base::RegularPolygon
.
2.2 XML de déclaration de plugin
Les étapes ci-dessus permettent de créer des instances de nos plugins une fois que la bibliothèque dans laquelle ils existent est chargée, mais le chargeur de plugins a toujours besoin d’un moyen de trouver cette bibliothèque et de savoir quoi référencer dans cette bibliothèque. À cette fin, nous allons également créer un fichier XML qui, avec une ligne d’exportation spéciale dans le manifeste du package, met toutes les informations nécessaires sur nos plugins à la disposition de la chaîne d’outils ROS.
Créez ros2_ws/src/polygon_plugins/plugins.xml
avec le code suivant :
<library path="polygon_plugins">
<class type="polygon_plugins::Square" base_class_type="polygon_base::RegularPolygon">
<description>This is a square plugin.</description>
</class>
<class type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon">
<description>This is a triangle plugin.</description>
</class>
</library>
Quelques points à noter :
La balise
library
donne le chemin relatif vers une bibliothèque qui contient les plugins que nous voulons exporter. Dans ROS 2, c’est juste le nom de la bibliothèque. Dans ROS 1, il contenait le préfixelib
ou parfoislib/lib
(c’est-à-direlib/libpolygon_plugins
) mais ici c’est plus simple.La balise
class
déclare un plugin que nous voulons exporter depuis notre bibliothèque. Passons en revue ses paramètres :
type
: le type complet du plugin. Pour nous, c’estpolygon_plugins::Square
.
base_class
: Le type de classe de base complet pour le plugin. Pour nous, c’estpolygon_base::RegularPolygon
.
description
: une description du plugin et de ce qu’il fait.
name
: il y avait un attribut de nom, mais il n’est plus nécessaire.
2.3 Déclaration du plugin CMake
La dernière étape consiste à exporter vos plugins via CMakeLists.txt
. C’est un changement par rapport à ROS 1, où l’exportation se faisait via package.xml
. Ajoutez le bloc suivant à votre ros2_ws/src/polygon_plugins/CMakeLists.txt
après la ligne indiquant find_package(pluginlib REQUIRED)
add_library(polygon_plugins src/polygon_plugins.cpp)
target_include_directories(polygon_plugins PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
ament_target_dependencies(
polygon_plugins
polygon_base
pluginlib
)
pluginlib_export_plugin_description_file(polygon_base plugins.xml)
install(
TARGETS polygon_plugins
EXPORT export_${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
Et avant la commande ament_package
, ajoutez
ament_export_libraries(
polygon_plugins
)
ament_export_targets(
export_${PROJECT_NAME}
)
Les arguments de cette commande CMake sont
Le package pour la classe de base, c’est-à-dire
polygon_base
Le chemin relatif vers la déclaration de plugin xml, c’est-à-dire
plugins.xml
3 Utilisez les plugins
Il est maintenant temps d’utiliser les plugins. Cela peut être fait dans n’importe quel package, mais ici nous allons le faire dans le package de base. Modifiez ros2_ws/src/polygon_base/src/area_node.cpp
pour contenir les éléments suivants :
#include <pluginlib/class_loader.hpp>
#include <polygon_base/regular_polygon.hpp>
int main(int argc, char** argv)
{
// To avoid unused parameter warnings
(void) argc;
(void) argv;
pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("polygon_base", "polygon_base::RegularPolygon");
try
{
std::shared_ptr<polygon_base::RegularPolygon> triangle = poly_loader.createSharedInstance("polygon_plugins::Triangle");
triangle->initialize(10.0);
std::shared_ptr<polygon_base::RegularPolygon> square = poly_loader.createSharedInstance("polygon_plugins::Square");
square->initialize(10.0);
printf("Triangle area: %.2f\n", triangle->area());
printf("Square area: %.2f\n", square->area());
}
catch(pluginlib::PluginlibException& ex)
{
printf("The plugin failed to load for some reason. Error: %s\n", ex.what());
}
return 0;
}
Le ClassLoader
est la classe clé à comprendre, définie dans le class_loader.hpp
header.
Il est modélisé avec la classe de base, c’est-à-dire
polygon_base::RegularPolygon
Le premier argument est une chaîne pour le nom du package de la classe de base, c’est-à-dire
polygon_base
Le deuxième argument est une chaîne avec le type de classe de base entièrement qualifié pour le plugin, c’est-à-dire
polygon_base::RegularPolygon
Il existe plusieurs façons d’instancier une instance de la classe. Dans cet exemple, nous utilisons des pointeurs partagés. Nous avons juste besoin d’appeler createSharedInstance
avec le type complet de la classe du plugin, dans ce cas, polygon_plugins::Square
.
Remarque importante : le package polygon_base
dans lequel ce nœud est défini ne dépend PAS de la classe polygon_plugins
. Les plugins seront chargés dynamiquement sans qu’aucune dépendance n’ait besoin d’être déclarée. De plus, nous instancions les classes avec des noms de plug-ins codés en dur, mais vous pouvez également le faire de manière dynamique avec des paramètres, etc.
4 Construire et exécuter
Revenez à la racine de votre espace de travail, ros2_ws
, et créez vos nouveaux packages :
colcon build --packages-select polygon_base polygon_plugins
À partir de ros2_ws
, assurez-vous de sourcer les fichiers d’installation :
. install/setup.bash
. install/setup.bash
call install/setup.bat
Exécutez maintenant le nœud :
ros2 run polygon_base area_node
Il devrait imprimer
Triangle area: 43.30
Square area: 100.00