本文的目的是解决如下几个问题:
ROS中的插件就是可以动态加载(运行时加载)的扩展功能类。实现的原理是基于C++多态。这些插件在ROS里catkin编译系统能够被认出,并且可以和其他node节点耦合。插件不需要提前链接到ROS的程序上,运行时候加载就可以调用其中功能。
插件机制使得开发者,不需要改动原软件的代码,直接将需要的功能通过插件进行扩展即可。
我们熟知的ROS中move_base
功能包,除了nav_core
外,其他所有功能包都是插件的形式。他们继承nav_core
的接口,来实现导航。
查询下所有nav_core
的插件:rospack plugins --attrib=plugin nav_core
如果我们想要研究替换一些ROS自带的路径规划算法,那么ROS插件必须要学习的。
为理解插件机制如何工作,我们按照步骤一步步来,具体实现如下图所示的一个ROS插件。
如果我们基于现有的基类来实现插件,这一步可以省略。比如,ros中move_base的nav_core
我们在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
函数实现参数初始化。
继承基类,实现统一接口
#ifndef SRC_POLYGON_PLUGINS_H
#define SRC_POLYGON_PLUGINS_H
#include
#include
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
#include "../include/learn_plugin/polygon_plugins.h"
// use macros of plyginlib to register
#include
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
)
下图中对应目录里,我们可以看到,已经编译生成了对应的.so动态链接库. 文件的命名是lib + <我们在cmakelists中add_library里面的命名>
接下来,需要将插件加到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>
关于上述xml文件中具体标签含义,本文在第三节中做详细阐述。
package.xml
文件修改:
<export>
<learn_plugin plugin="${prefix}/polygon_plugins.xml" />
export>
测试ROS是否检索:
我们在src目录下,新建文件polygon_loader.cpp
,开始调用上述生成的插件。
最终的功能包目录如下图所示:
//
// Created by xu on 2021/1/11.
//
//
// include
#include
#include
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
中如果没有指定name
tag(如本例中的learn_plugin/regular_triangle
),直接使用类真实的type:polygon_plugins::Triangle
也可以。
我们再来回顾下从一到六小节中经历了什么。
首先我们创建了基类,定义了插件应该实现哪些功能。
然后在功能类中,我们继承了基类,面向具体类型实现基类中定义的功能。
同时在功能类对应的cpp文件中,我们需要申明注册创建好的插件。
最后需要将插件编译成为lib文件,并注册到ROS中,使得catkin系统能够直接查找到对应的动态链接库。
在应用的时候,我们需要用到ros中pluginlib函数库,创建一个ClassLoader,用来加载plugin。
第二节中,我们编写了一份plugin描述文件。
本节,我们对其中具体标签的含义做如下解释:
该标签允许多个包含插件的libraries
该标签允许包含多个插件
Attributes:
Attributes:
单个插件的lib描述文件:
<library path="lib/libplugin">
<class name="MyPlugin" type="my_namespace::MyPlugin" base_class_type="interface_namespace::PluginInterface">
<description>
A description of MyPlugin
description>
class>
library>
多个插件的lib描述文件:
<library path="lib/libplugin">
<class name="FirstPlugin" type="my_namespace::FirstPlugin" base_class_type="interface_namespace::PluginInterface">
<description>
A description of FirstPlugin
description>
class>
<class name="SecondPlugin" type="my_namespace::SecondPlugin" base_class_type="interface_namespace::PluginInterface">
<description>
A description of SecondPlugin
description>
class>
library>
多个lib的插件描述文件:
<class_libraries>
<library path="lib/libplugina">
<class name="MyPluginA" type="my_namespacea::MyPluginA" base_class_type="interface_namespace::PluginInterface">
<description>
A description of MyPluginA
description>
class>
library>
<library path="lib/libpluginb">
<class name="MyPluginB" type="my_namespaceb::MyPluginB" base_class_type="interface_namespace::PluginInterface">
<description>
A description of MyPluginB
description>
class>
library>
class_libraries>
http://wiki.ros.org/pluginlib
http://wiki.ros.org/pluginlib/PluginDescriptionFile