ROS进阶功能—plugin插件机制
在ROS开发中,常常会接触到一个名词——plugin(插件)。这个名词在计算机软件开发中也经常提到。简单来说,ROS中的插件就是可以动态加载的扩展功能类。ROS中的pluginlib功能包提供了加载和卸载plugin的C++库,开发者在使用插件时,不需要考虑plugin类的链接位置,只需要将插件注册到pluginlib中,即可直接动态加载。这种插件机制非常方便,开发者不需要改动原软件的代码,直接将需要的功能通过插件进行扩展即可。
一、工作原理
假设ROS功能包中已经存在一个polygon的基类(polygon_interface_package),可以通过插件机制实现两种polygon的功能支持:rectangle_plugin(rectangle_plugin_package)和triangle_plugin(triangle_plugin_package)。在这两个功能包的package.xml 中,需要声明polygon_interface_package中的基类polygon,在编译过程中会把插件注册到ROS中,用户可以直接通过rospack命令进行全局插件查询,也可以在开发中直接使用这些插件。
二、实现一个插件
pluginlib 利用了C++多态的特性,不同的插件只要使用统一的接口就可以替换使用。用户在使用过程中也不需要修改代码或者重新编译,选择需要使用的插件即可扩展相应的功能。一般来讲,要实现一个插件,主要分为以下几个步骤。
1)创建基类,定义统一接口(如果基于现有基类实现插件,则不需要这个步骤)。
2)创建plugin类,继承基类,实现统一的接口。
3)注册插件。
4)编译生成插件的动态链接库。
5)将插件加入ROS中。
三、创建基类
在include目录下,新建头文件polygon_base.h来创建基类。
#ifndef SRC_POLYGON_BASE_H
#define SRC_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 //SRC_POLYGON_BASE_H
其中,插件要求构造函数不可以带参数,所以使用initialize函数实现参数初始化。
四、创建plugin类
继承基类,实现统一接口
#ifndef SRC_POLYGON_PLUGINS_H
#define SRC_POLYGON_PLUGINS_H
#include <learn_plugin/polygon_base.h>
#include <cmath>
namespace polygon_plugins{
class Triangle: public polygon_base::RegularPolygon
{
public:
// cnstructor
Triangle(): side_length_() {}
void initialize(double side_length){
side_length_ = side_length;
}
double area(){
return 0.5 * side_length_ * getHight();
}
double getHight(){
return sqrt((side_length_ * side_length_) - ((side_length_/2) * (side_length_/2)));
}
private:
double side_length_;
};
class Square : public polygon_base::RegularPolygon
{
public:
Square():side_length_(){}
void initialize(double side_length){
side_length_ = side_length;
}
double area(){
return side_length_ * side_length_;
}
private:
double side_length_;
};
}
#endif //SRC_POLYGON_PLUGINS_H
五、注册插件
#include <learn_plugin/polygon_base.h>
#include "../include/learn_plugin/polygon_plugins.h"
// use macros of plyginlib to register
#include <pluginlib/class_list_macros.h>
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
六、编译插件的动态链接库
修改CMakeLists文件,从而将之编译为动态链接库
include_directories(
include
${catkin_INCLUDE_DIRS}
)
add_library(polygon_plugins
src/polygon_plugins.cpp
)
七、将插件加入ROS
接下来,需要将插件加到ROS中,使得catkin系统能够查找到该插件。
分别在功能包下编辑插件对应的xml描述文件polygon_plugins.xml,然后修改package.xml 文件
新建polygon_plugins.xml 文件:
<library path="lib/libpolygon_plugins">
<class name="learn_plugin/regular_triangle" type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon">
<description>This is a triangle plugin.</description>
</class>
<class name="learn_plugin/regular_square" type="polygon_plugins::Square" base_class_type="polygon_base::RegularPolygon">
<description>This is a square plugin.</description>
</class>
</library>
package.xml 文件修改:
<export>
<!-- Other tools can request additional information be placed here -->
<learn_plugin plugin="${prefix}/polygon_plugins.xml" />
</export>
测试ROS是否检索:
八、调用插件
在src目录下,新建文件polygon_loader.cpp,开始调用上述生成的插件。
最终的功能包目录如下图所示:
//
// Created by xu on 2021/1/11.
//
//
// include <boost/shared_ptr.hpp>
#include <pluginlib/class_loader.h>
#include <learn_plugin/polygon_base.h>
int main(int argc, char** argv)
{
// 创建一个ClassLoader,用来加载plugin
pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("learn_plugin", "polygon_base::RegularPolygon");
try
{
// 加载Triangle插件类,路径在polygon_plugins.xml中定义
boost::shared_ptr<polygon_base::RegularPolygon> triangle = poly_loader.createInstance("learn_plugin/regular_triangle");
// 初始化边长
triangle->initialize(10.0);
ROS_INFO("Triangle area: %.2f", triangle->area());
}
catch(pluginlib::PluginlibException& ex)
{
ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
}
try
{
boost::shared_ptr<polygon_base::RegularPolygon> square = poly_loader.createInstance("learn_plugin/regular_square");
square->initialize(10.0);
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;
}
注意:
加载Triangle插件类的时候,polygon_plugins.xml 中如果没有指定nametag(如本例中的learn_plugin/regular_triangle),直接使用类真实的type:polygon_plugins::Triangle也可以。
九、plugin描述文件
1、 class_libraries标签
该标签允许多个包含插件的libraries
2、library 标签
该标签允许包含多个插件
Attributes:
- path : 从该功能包到library的相对路径
3、class标签
Attributes:
- name : 类的查找名.作为插件的标志被插件库使用
- type : 功能类的类型全名
- base_class_type : 基类的类型全名
- description :描述功能类的功能