在开发中需要使用到插件,因此学习了下pluginlib的一些使用,学习的还不够透彻,先记录一下这几天的学习结果。
关于ROS中pluginlib的使用主要参考的是《ROS的pluginlib的理解与实例》与《ROS专题----pluginlib简明笔记》这两篇文章。第一篇中讲述了一个示例,第二篇是ROS官方中对于pluginlib使用的一些解释的翻译。
简单实现一个ROS pluginlib可以参考第一篇文章中的示例,是可以直接跑通的,下述示例部分摘自该处,仅作记录:
示例
首先, 当然是在自己的工作空间中创建一个用于尝试的ROS Package. 依次输入下述命令.
$ roscd$ cd ../src$ catkin_create_pkg plugin_test roscpp pluginlib
OK, 现在可以开始写代码了. 在include/plugin_test文件夹下新建文件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
在include/plugin_test文件夹下新建文件polygon_plugins.h, 将下述代码拷贝进去, 申明我们的插件:
#ifndef PLUGINLIB_TUTORIALS__POLYGON_PLUGINS_H_#define PLUGINLIB_TUTORIALS__POLYGON_PLUGINS_H_#include <plugin_test/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_;};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_;};};#endif
在src文件夹下创建文件polygon_plugins.cpp, 并拷贝下述代码进去, 注册我们的插件.
#include <pluginlib/class_list_macros.h>#include <plugin_test/polygon_base.h>#include <plugin_test/polygon_plugins.h>PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
在CMakeLists.txt文件中, 加入下述add_library申明. 值得注意的是, 在CMakeLists.txt文件中, 需要在include_directories中添加include目录, 否则我们前面写的两个头文件将会找不到.
include_directories(${catkin_INCLUDE_DIRS}include)... ...add_library(polygon_plugins src/polygon_plugins.cpp)
如前所述, 咱还需要编辑关于插件的信息内容, 在plugin_test主目录下, 创建一个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>
在同目录下的package.xml文件中export tag块中添加下述内容:
<plugin_test plugin="${prefix}/polygon_plugins.xml" />
在命令行中运行下述指令, 对应的输出信息如下所示:
$ rospack plugins --attrib=plugin plugin_test
plugin_test /home/silence/WorkSpace/catkin_ws/src/plugin_test/polygon_plugins.xml
如果得到类似的输出, 则表示所有都是没问题的.
使用Plugin:在src目录下, 新建polygon_loader.cpp文件, 用于测试, 复制下述内容:
#include <pluginlib/class_loader.h>#include <plugin_test/polygon_base.h>int main(int argc, char** argv){pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("plugin_test", "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;}
并在CMakeLists.txt中加入下述申明, 然后 $ catkin_make.
add_executable(polygon_loader src/polygon_loader.cpp)target_link_libraries(polygon_loader ${catkin_LIBRARIES})
编译成功后, 运行节点, 应该会得到下述类似的输出.
$ rosrun plugin_test polygon_loader
[ INFO] [1477584281.637794959]: Triangle area: 43.30
[ INFO] [1477584281.637923253]: Square area: 100.00
进一步思考
在跑通上面的示例的时候,同时也思考了一个问题:怎么实现让主程序与注册的插件处与不同的文件夹下?作为一个快插快换的存在,灵活性一定是最重要的。那么肯定是需要程序间调用,所以两者的拆分很重要,最后经过了一些尝试以及资料的查询总算是实现了这个需求。
这里最主要是要明确插件描述文件:插件描述文件是用于存储所有关于在机器可读格式的插件的重要信息的XML文件。它包含有关插件所在的库的信息,插件的名称,插件的类型等。我们需要这个文件除了代码宏,允许ROS系统自动发现,加载和推理插件。插件描述文件还包含重要信息,如插件的描述,不适合在宏中。也就是上面的这部分:
<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>
为了让pluginlib查询跨所有ROS包的系统上的所有可用插件,每个包必须显式指定它导出的插件,以及哪些包库包含这些插件。一个插件提供者必须指向它的插件描述文件,在其package.xml中的内外销标签块。注意,如果您有其他导出,他们都必须在同一导出字段。对应上面的:
<plugin_test plugin="${prefix}/polygon_plugins.xml" />
这两块内容都是用于ROS中注册插件时使用的而不是调用插件时使用的,因此要写在注册的包内,这个在一开始自己写的时候没有理解因此一直不能跑起来,后面去看了第二篇文章后才略微理解了一些,后面修改完后实现了需要的功能。修改后的实现参见示例二:
示例2
建立一个插件文件夹
在自己的工作空间中创建一个用于尝试的ROS Package. 依次输入下述命令.
$ roscd$ cd ../src$ catkin_create_pkg plugin_node roscpp pluginlib
在include/plugin_node文件夹下新建文件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
在include/plugin_node文件夹下新建文件polygon_plugins.h, 将下述代码拷贝进去, 申明我们的插件:
#ifndef PLUGINLIB_TUTORIALS__POLYGON_PLUGINS_H_#define PLUGINLIB_TUTORIALS__POLYGON_PLUGINS_H_#include <plugin_node/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_;};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_;};};#endif
在src文件夹下创建文件polygon_plugins.cpp, 并拷贝下述代码进去, 注册我们的插件.
#include <pluginlib/class_list_macros.h>#include <plugin_node/polygon_base.h>#include <plugin_node/polygon_plugins.h>PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
在CMakeLists.txt文件中, 加入下述add_library申明:
include_directories(${catkin_INCLUDE_DIRS}include)... ...add_library(polygon_plugins src/polygon_plugins.cpp)
编辑关于插件的信息内容, 在plugin_test主目录下, 创建一个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>
在同目录下的package.xml文件中export tag块中添加下述内容:
<plugin_test plugin="${prefix}/polygon_plugins.xml" />
这一部分跟前面第一部分的前面一半基本是一致的。
建立一个调用插件的文件
在前面部分我们已经完成了一个插件的建立,这里我们重新建立一个文件夹去调用上述的插件:
$ roscd$ cd ../src$ catkin_create_pkg plugin_used roscpp pluginlib
在include/plugin_used文件夹下新建文件polygon_used.h, 将下述代码拷贝进去:
#ifndef PLUGINLIB_TUTORIALS__POLYGON_USED_H_
#define PLUGINLIB_TUTORIALS__POLYGON_USED_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_used.cpp文件, 用于测试, 复制下述内容:
#include <pluginlib/class_loader.h>
#include <plugin_used/polygon_used.h>int main(int argc, char** argv)
{pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("plugin_node", "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 is: %.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;
}
并在CMakeLists.txt中加入下述申明, 然后 $ catkin_make.
add_executable(polygon_loader src/polygon_loader.cpp)target_link_libraries(polygon_loader ${catkin_LIBRARIES})
编译成功后, 运行节点, 会得到下述类似的输出.
$ rosrun plugin_test polygon_loader
[ INFO] [1477584281.637794959]: Triangle area: 43.30
[ INFO] [1477584281.637923253]: Square area: 100.00
按照上述的方式,可以把插件与执行文件隔离开,即使需要修改插件也不用重新编译执行文件,可以实现一个插件快速修改或者加载的功能。很大程序上的提高了程序的灵活性。