ROS2 Jazzy:创建并使用插件(C++)(ros创建package)
目标
学习使用pluginlib创建并加载一个简单的插件。
背景信息
本教程源自 http://wiki.ros.org/pluginlib 和 编写并使用简单插件教程(https://wiki.ros.org/pluginlib/Tutorials/Writing%20and%20Using%20a%20Simple%20Plugin)。
pluginlib是一个用于在 ROS 包中加载和卸载插件的 C++ 库。插件是可动态加载的类,它们从运行时库(即共享对象、动态链接库)中加载。使用pluginlib时,你不用将应用程序与包含这些类的库进行显式链接,相反,pluginlib可以在任何时候打开一个包含导出类的库,而应用程序无需事先了解该库或包含类定义的头文件。插件对于在无需应用程序源代码的情况下扩展或修改应用程序行为非常有用。
先决条件
本文假定你具备基本的 C++ 知识,并且已经成功安装了 ROS2。
操作步骤
在本教程中,你将创建两个新的包,一个用于定义基类,另一个用于提供插件。基类将定义一个通用的多边形类,然后我们的插件将定义具体的形状。
1. 创建基类包
在你的ros2_ws/src文件夹中使用以下命令创建一个新的空包:
ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies pluginlib --node-name area_node polygon_base
打开你喜欢的编辑器,新建ros2_ws/src/polygon_base/include/polygon_base/regular_polygon.hpp,并将以下内容粘贴进去:
#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
上面的代码创建了一个名为RegularPolygon的抽象类。需要注意的是,这里有一个initialize方法。使用pluginlib时,需要一个无参数的构造函数,所以如果类需要任何参数,我们使用initialize方法将它们传递给对象。
我们需要让其他类能够使用这个头文件,所以打开ros2_ws/src/polygon_base/CMakeLists.txt进行编辑。在ament_target_dependencies命令之后添加以下行:
install(
DIRECTORY include/
DESTINATION include
)
并在ament_package命令之前添加这个命令:
ament_export_include_directories(
include
)
我们稍后会回到这个包来编写测试节点。
2. 创建插件包
现在我们要为抽象类编写两个非虚实现。在你的ros2_ws/src文件夹中使用以下命令创建第二个空包:
ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies polygon_base pluginlib --library-name polygon_plugins polygon_plugins
2.1 插件的源代码
打开ros2_ws/src/polygon_plugins/src/polygon_plugins.cpp进行编辑,并将以下内容粘贴进去:
#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)
Square和Triangle类的实现相当简单:保存边长,然后用它来计算面积。唯一与pluginlib相关的部分是最后三行,它们调用了一些神奇的宏,将这些类注册为实际的插件。下面来看看PLUGINLIB_EXPORT_CLASS宏的参数:
- 插件类的全限定类型,在这个例子中是polygon_plugins::Square。
- 基类的全限定类型,在这个例子中是polygon_base::RegularPolygon。
2.2 插件声明 XML 文件
上述步骤使得包含这些插件的库被加载时能够创建插件实例,但插件加载器仍然需要某种方法来找到该库,并知道在该库中引用什么。为此,我们还将创建一个 XML 文件,再结合包清单中的一个特殊导出行,使得 ROS 工具链能够获取插件必要信息。
创建ros2_ws/src/polygon_plugins/plugins.xml,内容如下:
<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>
需要注意几点:
- library标签给出了包含我们要导出的插件的库的相对路径。在 ROS2 中,这只是库的名称。在 ROS1 中,它包含前缀lib,有时是lib/lib(例如lib/libpolygon_plugins),但这里更简单。
- class标签声明了我们要从库中导出的一个插件。下面看看它的参数:
- type:插件的全限定类型。对我们来说,就是polygon_plugins::Square。
- base_class:插件的全限定基类类型。对我们来说,就是polygon_base::RegularPolygon。
- description:对插件及其功能的描述。
2.3 CMake 插件声明
最后一步是通过CMakeLists.txt导出你的插件。这与 ROS1 不同,ROS1 中是导出是通过package.xml完成的。在ros2_ws/src/polygon_plugins/CMakeLists.txt中find_package(pluginlib REQUIRED)这一行之后添加以下行:
pluginlib_export_plugin_description_file(polygon_base plugins.xml)
pluginlib_export_plugin_description_file命令的参数如下:
- 包含基类的包,即polygon_base。
- 插件声明 XML 文件的相对路径,即plugins.xml。
3. 使用插件
现在是使用插件的时候了。这可以在任何包中完成,不过在这里我们将在基类包中进行。新建ros2_ws/src/polygon_base/src/area_node.cpp,使其包含以下内容:
#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;
}
ClassLoader是需要理解的关键类,它在class_loader.hpp头文件中定义:
- 它使用基类进行模板化,即polygon_base::RegularPolygon。
- 第一个参数是基类所在包的名称的字符串,即polygon_base。
- 第二个参数是插件的全限定基类类型的字符串,即polygon_base::RegularPolygon。
有多种方法可以实例化这个类的对象。在这个例子中,我们使用智能指针。我们只需要调用createSharedInstance并传入插件类的全限定类型,在这个例子中是polygon_plugins::Square。
重要提示:定义这个节点的polygon_base包并不依赖于polygon_plugins类。插件将被动态加载,无需声明任何依赖关系。此外,我们使用硬编码的插件名称来实例化类,但你也可以使用参数等动态地进行实例化。
4. 编译并运行
回到你的工作空间根目录ros2_ws,编译你的新包:
colcon build --packages-select polygon_base polygon_plugins
在ros2_ws目录下,确保加载设置文件:
# Linux/macOS
source install/setup.bash
现在运行节点:
ros2 run polygon_base area_node
它应该输出:
Triangle area: 43.30
Square area: 100.00
总结
恭喜!你刚刚编写并使用了你的第一个插件。
关注【智践行】公众号,发送 【机器人】 获得机器人经典学习资料