本文的目的是解决如下几个问题:

  1. 清楚ros帮我们做了哪些便利工作?与传统C++编写多态代码有多少区别?
  2. 如何自定义一个插件基类,如何替换别人的插件?

一、 概念

ROS中的插件就是可以动态加载(运行时加载)的扩展功能类。实现的原理是基于C++多态。这些插件在ROS里catkin编译系统能够被认出,并且可以和其他node节点耦合。插件不需要提前链接到ROS的程序上,运行时候加载就可以调用其中功能。

插件机制使得开发者,不需要改动原软件的代码,直接将需要的功能通过插件进行扩展即可。

我们熟知的ROS中move_base功能包,除了nav_core外,其他所有功能包都是插件的形式。他们继承nav_core的接口,来实现导航。

查询下所有nav_core的插件:rospack plugins --attrib=plugin nav_core

在这里插入图片描述

如果我们想要研究替换一些ROS自带的路径规划算法,那么ROS插件必须要学习的。

二、 实例

为理解插件机制如何工作,我们按照步骤一步步来,具体实现如下图所示的一个ROS插件。

在这里插入图片描述

1. 创建基类,定义统一接口

如果我们基于现有的基类来实现插件,这一步可以省略。比如,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函数实现参数初始化。

2. 创建插件类

继承基类,实现统一接口

#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

3. 注册插件

#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)

4. 编译生成插件的动态链接库

修改CMakeLists文件,从而将之编译为动态链接库

include_directories(
 include
  ${catkin_INCLUDE_DIRS}
)

 add_library(polygon_plugins
   src/polygon_plugins.cpp
 )

下图中对应目录里,我们可以看到,已经编译生成了对应的.so动态链接库. 文件的命名是lib + <我们在cmakelists中add_library里面的命名>
在这里插入图片描述

5. 将插件加入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>

关于上述xml文件中具体标签含义,本文在第三节中做详细阐述。

package.xml文件修改:

  <export>
    <!-- Other tools can request additional information be placed here -->
    <learn_plugin plugin="${prefix}/polygon_plugins.xml" />
  </export>

测试ROS是否检索:

在这里插入图片描述

6. 调用插件

我们在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也可以。

7. 流程小结

我们再来回顾下从一到六小节中经历了什么。

首先我们创建了基类,定义了插件应该实现哪些功能。
然后在功能类中,我们继承了基类,面向具体类型实现基类中定义的功能。
同时在功能类对应的cpp文件中,我们需要申明注册创建好的插件。
最后需要将插件编译成为lib文件,并注册到ROS中,使得catkin系统能够直接查找到对应的动态链接库。


在应用的时候,我们需要用到ros中pluginlib函数库,创建一个ClassLoader,用来加载plugin。

三、 plugin描述文件

第二节中,我们编写了一份plugin描述文件。

本节,我们对其中具体标签的含义做如下解释:

1. class_libraries标签

该标签允许多个包含插件的libraries

2. library 标签

该标签允许包含多个插件

Attributes:

  • path : 从该功能包到library的相对路径

3. class标签

Attributes:

  • name : 类的查找名.作为插件的标志被插件库使用
  • type : 功能类的类型全名
  • base_class_type : 基类的类型全名
  • description :描述功能类的功能

4. 举例

单个插件的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