ROS探索总结(三十三)——pluginlib

  • 内容
  • 评论
  • 相关

ROS的开发中,常常会接触到一个名词——插件(plugin)。这个名词在计算机软件开发中是常常会提到的,具体含义可以参考百度百科的插件词条。在ROS中,插件的概念类似,简单来讲,ROS中的插件(plugin)就是可以动态加载的扩展功能类。ROS中的pluginlib功能包,提供了加载和卸载pluginC++库,开发者在使用plugin时,不需要考虑plugin类的链接位置,只需要将plugin注册到pluginlib中,即可直接动态加载。这种插件机制非常方便,开发者不需要改动原本软件的代码,直接将需要的功能通过plugin进行扩展即可。本文带你走近plugin,探索如何实现一个简单的plugin

一、示

我们首先通过下边这张图来了解一下pluginlib的工作原理

plugin_model_副本

假设ROS的功能包中已经存在一个polygon的基类(polygon_interface_package),我们可以通过plugin来实现两种polygon的功能支持:rectangle_pluginrectangle_plugin_package)和triangle_plugintriangle_plugin_package),在这两个功能包的package.xml中,需要声明polygon_interface_package中的基类polygon,然后在编译的过程中,会把插件注册到ROS系统,用户可以直接通过rospack的命令进行全局的插件查询,也可以在开发中直接使用这些插件了

 

二、如何实现一个插

pluginlib利用了C++多态的特性,不同的插件只要使用统一的接口,就可以替换使用,用户在使用过程中也不需要修改代码或者重新编译,选择需要使用的插件即可扩展相应的功能。一般来讲,实现一个插件主要需要以下几个步骤

  1. 创建基类,定义统一的接口。如果是基于现有的基类实现plugin,则不需要这个步骤
  2. 创建plugin类,继承基类,实现统一的接口
  3. 注册插
  4. 编译生成插件的动态链接
  5. 将插件加入ROS

接下来,我们就根据这几个步骤来实现第一节图示中的plugin功能,在开始之前,你需要建立一个pluginlib_tutorials的功能包,添加依赖pluginlib

  1. $ catkin_create_pkg pluginlib_tutorials roscpp pluginlib

完整的功能包代码可以在github上下载

 

三、创建基

首先我们来创建一个polygon的基类(polygon_base.h),定义了一些简单的接口,需要注意的是initialize()这个接口的作用

  1. #ifndef PLUGINLIB_TUTORIALS_POLYGON_BASE_H_
  2. #define PLUGINLIB_TUTORIALS_POLYGON_BASE_H_
  3.  
  4. namespace polygon_base
  5. {
  6.   class RegularPolygon
  7.   {
  8.     public:
  9.       //pluginlib要求构造函数不能带有参数,所以定义initialize来完成需要初始化的工作
  10.       virtual void initialize(double side_length) = 0;
  11.  
  12.       //计算面积的接口函数
  13.       virtual double area() = 0;
  14.  
  15.       virtual ~RegularPolygon(){}
  16.  
  17.     protected:
  18.       RegularPolygon(){}
  19.   };
  20. };
  21. #endif

四、创建plugin

接下来我们来创建rectangle_plugintriangle_plugin类(polygon_plugins.h),实现基类的接口,也可以添加plugin自己需要的接口

  1. #ifndef PLUGINLIB_TUTORIALS_POLYGON_PLUGINS_H_
  2. #define PLUGINLIB_TUTORIALS_POLYGON_PLUGINS_H_
  3. #include <pluginlib_tutorials/polygon_base.h>
  4. #include <cmath>
  5.  
  6. namespace polygon_plugins
  7. {
  8.   class Triangle : public polygon_base::RegularPolygon
  9.   {
  10.     public:
  11.       Triangle() : side_length_() {}
  12.  
  13.       // 初始化边长
  14.       void initialize(double side_length)
  15.       {
  16.         side_length_ = side_length;
  17.       }
  18.  
  19.       double area()
  20.       {
  21.         return 0.5 * side_length_ * getHeight();
  22.       }
  23.  
  24.       // Triangle类自己的接口
  25.       double getHeight()
  26.       {
  27.         return sqrt((side_length_ * side_length_) - ((side_length_ / 2) * (side_length_ / 2)));
  28.       }
  29.  
  30.     private:
  31.       double side_length_;
  32.   };
  33.  
  34.   class Square : public polygon_base::RegularPolygon
  35.   {
  36.     public:
  37.       Square() : side_length_() {}
  38.  
  39.       // 初始化边长
  40.       void initialize(double side_length)
  41.       {
  42.         side_length_ = side_length;
  43.       }
  44.  
  45.       double area()
  46.       {
  47.         return side_length_ * side_length_;
  48.       }
  49.  
  50.     private:
  51.       double side_length_;
  52.  
  53.   };
  54. };
  55. #endif

五、注册插

上边两步我们就实现了两个简单插件的主要代码,接下来我们还需要创建一个cpp文件(polygon_plugins.cpp),来注册插件,声明我们创建了两个插件 

  1. //包含pluginlib的头文件,使用pluginlib的宏来注册插件
  2. #include <pluginlib/class_list_macros.h>
  3. #include <pluginlib_tutorials/polygon_base.h>
  4. #include <pluginlib_tutorials/polygon_plugins.h>
  5.  
  6. //注册插件,宏参数:plugin的实现类,plugin的基类
  7. PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon);
  8. PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon);

六、编译插件的动态链接

为了编译插件的功能包,需要修改CMakefile.txt文件,加入下边两行编译规则,将插件编译成动态链接库

  1. include_directories(include)
  2. add_library(polygon_plugins src/polygon_plugins.cpp)

现在可以编译功能包了,但是为了便于开发者使用plugin,还需要编写xml文件,将插件加入ROS系统

 

七、将插件加入ROS

这里需要添加和修改功能包根目录下的两个xml文件

7.1 polygon_plugins.xml

  1. <library path="lib/libpluginlib_tutorials">
  2.   <class name="pluginlib_tutorials/regular_triangle" type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon">
  3.     <description>This is a triangle plugin.</description>
  4.   </class>
  5.   <class name="pluginlib_tutorials/regular_square" type="polygon_plugins::Square" base_class_type="polygon_base::RegularPolygon">
  6.     <description>This is a square plugin.</description>
  7.   </class>
  8. </library>

可以看到,这个xml文件主要描述了plugin的动态库路径、实现类、基类、描述等信息。

 

7.2 package.xml

在package.xml中添加下边的两行代码:

  1. <export>
  2.   <pluginlib_tutorials plugin="${prefix}/polygon_plugins.xml" /> 
  3. </export>

然后我们可以通过下边的命令来查看功能包的插件路径:

  1. rospack plugins --attrib=plugin pluginlib_tutorials

如果没有问题,会出现如下的结果

clip_image003

 

八、调用插

在上边的步骤中,我们已经实现了插件的所有代码,接下来,我们就来尝试调用这两个插件,先来看一下代码(polygon_loader.cpp

  1. #include <boost/shared_ptr.hpp>
  2.  
  3. #include <pluginlib/class_loader.h>
  4. #include <pluginlib_tutorials/polygon_base.h>
  5.  
  6. int main(int argc, char** argv)
  7. {
  8.   // 创建一个ClassLoader,用来加载plugin
  9.   pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("pluginlib_tutorials", "polygon_base::RegularPolygon");
  10.  
  11.   try
  12.   {
  13.     // 加载Triangle插件类,路径在polygon_plugins.xml中定义
  14.     boost::shared_ptr<polygon_base::RegularPolygon> triangle = poly_loader.createInstance("pluginlib_tutorials/regular_triangle");
  15.  
  16.     // 初始化边长
  17.     triangle->initialize(10.0);
  18.  
  19.     ROS_INFO("Triangle area: %.2f", triangle->area());
  20.   }
  21.   catch(pluginlib::PluginlibException& ex)
  22.   {
  23.     ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
  24.   }
  25.  
  26.   try
  27.   {
  28.     boost::shared_ptr<polygon_base::RegularPolygon> square = poly_loader.createInstance("pluginlib_tutorials/regular_square");
  29.     square->initialize(10.0);
  30.  
  31.     ROS_INFO("Square area: %.2f", square->area());
  32.   }
  33.   catch(pluginlib::PluginlibException& ex)
  34.   {
  35.     ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());
  36.   }
  37.  
  38.   return 0;
  39. }

从上边的代码中我们可以看到,plugin可以在程序中动态加载,成功加载之后就可以调用plugin的接口来实现相应的功能了

修改CMakefile.txt,添加上边代码的编译规则

  1. add_executable(polygon_loader src/polygon_loader.cpp) 
  2. target_link_libraries(polygon_loader ${catkin_LIBRARIES})

然后编译并运行,可以看到如下结果

clip_image004

 

OK,现在我们就完成了插件的实现和调用了,是不是还比较简单。在实际应用中,我们需要根据需求实现插件更多的扩展功能,但是基本原理仍然相同。下一篇我们将会探索如何实现一个rviz的插件,敬请期待

 

参考资料

1. <ROS> pluginlib理解与示例

2. ROS-----pluginlib使用总结

 


原创文章,转载请注明: 转载自古月居

本文链接地址: ROS探索总结(三十三)——pluginlib

微信 OR 支付宝 扫描二维码
为本文作者 打个赏
pay_weixinpay_weixin

评论

13条评论
  1. Gravatar 头像

    CHRIS 回复

    古神,我刚学习ros不久,请问我在利用图像信息控制机器人行动时,算法应该写成plugin还是node?我不太懂这两个的区别

    • 古月

      古月 回复

      @CHRIS 要写node,plugin是往ROS中已有的功能里做集成的,需要有接口。

      • Gravatar 头像

        CHRIS 回复

        @古月 明白了,你的回答好清楚,多谢!这个利用node订阅图像信息进行图像处理我大概有思路,但是控制部分的代码还真是有点难想,古神可以给我推荐个PID或其他的控制算法的模板吗?我目前是在gazebo上做仿真

  2. Gravatar 头像

    露露 回复

    你好,costmap_2d这个包似乎可以建立代价地图,但一直不知道怎么用,能指明一下方向吗?或是代码例子?

    • 古月

      古月 回复

      @露露 导航的例子可以参考turtlebot_gazebo等机器人的仿真案例

  3. Gravatar 头像

    alex 回复

    火钳留名

  4. Gravatar 头像

    ierent 回复

    古大神,你好,一直想学习ROS的架构,想阅读下master有关topic,节点等管理的源代码,但是一直没有找到,git上的源码也不知道那一部分代码是这个。麻烦给个提示、

      • Gravatar 头像

        ierent 回复

        @古月 好的 谢谢,我最近看 如果节点间利用网络传输图片是不是 会耗时过大呢?

        • 古月

          古月 回复

          @ierent 这个要看图像的数据量和网络带宽,可以计算一下每秒的数据量

  5. Gravatar 头像

    海漩涡 回复

    月哥发现个错误:

    ----------------------------------------------
    package.xml

    ----------------------------------------------------

    pluginlib_tutorials_ 这个包名最后多了个下划线

  6. Gravatar 头像

    海漩涡 回复

    学习了

发表评论

电子邮件地址不会被公开。 必填项已用*标注