ROS插件机制
简介
pluginlib是一个C++库,用于从ROS包中加载和卸载插件插件库。插件是动态加载的类,它们从运行时库(即共享对象,动态链接库)加载。pluginlib可以在任何时候打开包含导出类的库,而不需要应用程序事先了解库或包含类定义的头文件。插件可用于扩展/修改应用程序行为,而无需修改应用程序源代码。
编写和使用简单的插件
ROS wiki 参考:pluginlib/Tutorials/Writing and Using a Simple Plugin
本篇对ROS wiki中的Writing and Using a Simple Plugin
插件机制教程翻译、逐步实现、讲解。
教程中创建了一个多边形的抽象类作为基类。
并实现了两个子类(矩形、三角形)作为插件提供实际功能(triangle_plugin、rectangle_plugin)——计算面积。
插件机制使用的大概步骤如下:
- 创建一个抽象基类,定义统一通用接口
如果基于现有基类实现插件,则不需要这个步骤。比如
navigation
中的costmap_2d
包中已经提供了代价地图costmap_2d::Layer
的基类。nav_core
包中提供了nav_core::BaseGlobalPlanner
、nav_core::BaseLocalPlanner
、nav_core::RecoveryBehavior
三个基类。分别用于全局路径规划、局部路径规划、复位行为加载。
- 创建plugin类,继承基类,实现统一的接口。
- 注册插件。
- 编译生成插件的动态链接库。
- 将插件加入ROS中。
下载pluginslib
sudo apt-get install ros-$ROS_DISTRO-pluginlib
创建示例包
创建pluginlib_tutorials_
包同时添加roscpp``pluginlib
依赖
cd ~/eduRobot_ws/src/
catkin_create_pkg pluginlib_tutorials_ roscpp pluginlib
或者直接用roboware studio在~/eduRobot_ws/src/
目录下直接创建包,并添加roscpp``pluginlib
依赖。
创建基类
我们将创建一个基类,所有插件都将继承该基类。对于这个例子,将创建两个不同种类的多边形RegularPolygon对象并使用它们,因此我们需要创建RegularPolygon抽象类作为基类。创建~/eduRobot_ws/src/pluginlib_tutorials_/include/pluginlib_tutorials_/polygon_base.h,polygon_base.h
中内容如下:
#ifndef PLUGINLIB_TUTORIALS__POLYGON_BASE_H_
#define PLUGINLIB_TUTORIALS__POLYGON_BASE_H_
namespace polygon_base
{
class RegularPolygon
{
public:
virtual void initialize(double side_length) = 0;
virtual double area() = 0;
virtual ~RegularPolygon(){}
protected:
RegularPolygon(){}
};
};
#endif
此处需要特别注意ROS中使用pluginlib类,要求构造函数没有参数。因此
需要使用initialize()
函数来初始化对象。
initialize(double side_length)
用于初始化并以多边形边长为参数
area()
用于输出多边形面积
创建插件类
下面我们将在这个例子中将基于基类RegularPolygon
创建两个插件类,第一个是Triangle,第二个是Square。
创建include/pluginlib_tutorials_/polygon_plugins.h
文件。
#ifndef PLUGINLIB_TUTORIALS__POLYGON_PLUGINS_H_
#define PLUGINLIB_TUTORIALS__POLYGON_PLUGINS_H_
#include <pluginlib_tutorials_/polygon_base.h>
#include <cmath>
namespace polygon_plugins
{
class Triangle : public polygon_base::RegularPolygon
{
public:
Triangle(){}
void initialize(double side_length){
side_length_ = side_length;
}
double area(){
return 0.5 * side_length_ * getHeight();
}
double getHeight(){
return sqrt((side_length_ * side_length_) - ((side_length_ / 2) * (side_length_ / 2)));
}
private:
double side_length_;
};//polygon_base::RegularPolygon
class Square : public polygon_base::RegularPolygon
{
public:
Square(){}
void initialize(double side_length){
side_length_ = side_length;
}
double area(){
return side_length_ * side_length_;
}
private:
double side_length_;
};//polygon_base::RegularPolygon
};//polygon_plugins
#endif
Triangle
类与Square
类分别实现了等边三角形、正方形的初始化:initialize()
、面积计算:area()
,Triangle
类还有getHeight()
函数,用于等边三角形的高的计算。
注册插件
到目前为止,我们刚刚创建了一些标准的C++类。现在,我们将开始执行pluginlib使用中特定的工作,
将Triangle和Square类声明为插件。
创建src/polygon_plugins.cpp
内容粘如下:
#include <pluginlib/class_list_macros.h>
#include <pluginlib_tutorials_/polygon_base.h>
#include <pluginlib_tutorials_/polygon_plugins.h>
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
#include <pluginlib/class_list_macros.h>
首先包含pluginlib宏,允许我们将类注册为插件。
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
在这里,我们将Triangle
类Square
类注册为插件。让我们来看看PLUGINLIB_EXPORT_CLASS()
宏的参数:
以Triangle为例:
- 插件类的类型全称,在本例中为
polygon_plugins::Triangle
。 - 基类的类型全称,在本例中为
polygon_base::RegularPolygon
。
构建插件动态链接库
将以下内容添加到CMakeLists.txt文件中,以构建插件库:
include_directories(include)
add_library(polygon_plugins src/polygon_plugins.cpp)
然后,就可以用catkin_make
编译工作空间。
将插件加入ROS中
上面的步骤使得插件的实例一旦加载就可以创建插件的实例对象,但是插件加载器仍然需要一种方法来查找该库并知道该库中的引用内容。为此,我们还将创建一个XML文件,并修改package.xml
。该文件与package.xml
一起,可以为ROS提供有关我们的插件的所有必要信息。
创建~/eduRobot_ws/src/pluginlib_tutorials_/polygon_plugins.xml
,内容如下:
<library path="lib/libpolygon_plugins">
<class type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon">
<description>This is a triangle plugin.</description>
</class>
<class type="polygon_plugins::Square" base_class_type="polygon_base::RegularPolygon">
<description>This is a square plugin.</description>
</class>
</library>
<library path="lib/libpolygon_plugins">
第一行中给出了包含插件类的动态链接库的相对路径。这个库就是上面在CMakeLists.txt
中添加的编译产物。
<class type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon">
<description>This is a triangle plugin.</description>
</class>
以Triangle
为例,<class>
标签声明了我们希望从我们的库导出插件。参数如下:
- type:插件的类型全称。对我们来说,这是
polygon_plugins::Triangle
。 - base_class:插件基类的类型全称。对我们来说,就是
polygon_base::RegularPolygon
。 - description:插件的描述。描述其作用。
完成polygon_plugins.xml
文件的创建后,还需要将这个文件在package.xml
中声明,将如下内容加入~/eduRobot_ws/src/pluginlib_tutorials_/package.xml
:
<export>
<pluginlib_tutorials_ plugin="${prefix}/polygon_plugins.xml"/>
</export>
标签的名称pluginlib_tutorials_
应该与插件的基类base_class所在的包对应。在这种情况下,基类和继承的插件类存在于同一个包中,但在大多数实际情况中,情况并非如此。
其中plugin
属性应设置为指向上面步骤中创建的的XML文件。
这样,插件的创建、声明就完成了。
要验证操作是否正常,请首先catkin_make构建工作区,然后尝试运行以下rospack命令:
rospack plugins --attrib=plugin pluginlib_tutorials_
没有异常的话你会看到如下输出:
pluginlib_tutorials_ /home/<user_name>/eduRobot_ws/src/pluginlib_tutorials_/polygon_plugins.xml
使用插件
我们已经成功创建并导出了一些RegularPolygon插件,现在来使用它们。创建src/polygon_loader.cpp
内容如下:
#include <pluginlib/class_loader.h>
#include <pluginlib_tutorials_/polygon_base.h>
int main(int argc, char** argv)
{
pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("pluginlib_tutorials_", "polygon_base::RegularPolygon");
try
{
boost::shared_ptr<polygon_base::RegularPolygon> triangle = poly_loader.createInstance("polygon_plugins::Triangle");
triangle->initialize(10.0);
boost::shared_ptr<polygon_base::RegularPolygon> square = poly_loader.createInstance("polygon_plugins::Square");
square->initialize(10.0);
ROS_INFO("Triangle area: %.2f", triangle->area());
ROS_INFO("Square area: %.2f", square->area());
}
catch(pluginlib::PluginlibException& ex)
{
ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
}
return 0;
}
首先我们需要include pluginlib/class_loader.h
头文件,以及polygon_base.h
。
我们创建一个名为poly_loader
的ClassLoader对象,它对应的是插件的基类,我们将用它来加载插件。它需要两个参数,第一个是包含插件基类的包名,在我们的例子中是pluginlib_tutorials_
。第二个是基类的完全限定类型,在我们的例子中是polygon_base::RegularPolygon
。
接下来可以通过createInstance()函数来关联对应插件,
细节可以查看plugin的API文档:
http://docs.ros.org/api/pluginlib/html/classpluginlib_1_1ClassLoader.html
boost::shared_ptr<polygon_base::RegularPolygon> triangle = poly_loader.createInstance("polygon_plugins::Triangle");
triangle->initialize(10.0);
我们加载了polygon_plugins::Triangle
插件。由于插件构造函数不能有参数,因此我们在构造对象之后立即调用initialize函数来初始化对象。
再在CMakeLists.txt
中添加polygon_loader
可执行文件:
add_executable(polygon_loader src/polygon_loader.cpp)
add_dependencies(polygon_loader ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(polygon_loader ${catkin_LIBRARIES})
接下来catkin_make编译后,运行polygon_loader
:
rosrun pluginlib_tutorials_ polygon_loader
有如下输出:
[ INFO] [1554170430.374275315]: Triangle area: 43.30
[ INFO] [1554170430.374307944]: Square area: 100.00