【场景描述】
pluginlib直译是插件库,所谓插件字面意思就是可插拔的组件,比如:以计算机为例,可以通过USB接口自由插拔的键盘、鼠标、U盘...都可以看作是插件实现,其基本原理就是通过规范化的USB接口协议实现计算机与USB设备的自由组合。同理,在软件编程中,插件是一种遵循一定规范的应用程序接口编写出来的程序,插件程序依赖于某个应用程序,且应用程序可以与不同的插件程序自由组合。在ROS中,也会经常使用到插件,场景如下:
- 导航插件:在导航中,涉及到路径规划模块,路径规划算法有多种,也可以自实现,导航应用时,可能需要测试不同算法的优劣以选择更合适的实现,这种场景下,ROS中就是通过插件的方式来实现不同算法的灵活切换的。
- rviz插件:在rviz中已经提供了丰富的功能实现,但是即便如此,特定场景下,开发者可能需要实现某些定制化功能并集成到rviz中,这一集成过程也是基于插件的。
【官方定义】
pluginlib是一个c++库, 用来从一个ROS功能包中加载和卸载插件(plugin)。插件是指从运行时库中动态加载的类。通过使用Pluginlib,不必将某个应用程序显式地链接到包含某个类库,Pluginlib可以随时打开包含类的库,而不需要应用程序事先知道包含类定义的库或者头文件。
【插件意义】
其实对于ROS-1初学者来说,写得最多的是什么?没错,是节点!因为ROSWIKI上的入门教程都是教你如何写节点程序的,比如一个发布者,或是一个服务器。但是随着你编写的程序越来越复杂,实现的功能模块越来越齐全,你就会发现写节点太过于分散了,每次需要启动一大堆的节点,而这些节点很可能在场景中的地位是平等且并行的,这时候你就会想到,有没有可能用一种方式把这些节点像浏览器书签一样管理,想用哪个就把哪个调出来,动态的进行实例化和加载使用。幸运的是,ROS-1开发者也预料到了这种使用场景,所以他们提供了pluginlib这个c++库,其底层就是帮我们实现了一套工厂模式!!
【编写插件】
- 定义一个基类
namespace general_plugin
{
class BasePlugin
{
public:
BasePlugin();
virtual ~BasePlugin();
public:
virtual void initialize(const std::string name) = 0;
virtual void start() = 0;
virtual void stop() = 0;
virtual void interrupt() = 0;
virtual void goon() = 0;
virtual int get_status() = 0;
};
}
- 定义一个插件类
头文件
namespace general_plugin
{
class MotionControl : public BasePlugin
{
public:
MotionControl();
~MotionControl();
public:
void initialize(const std::string name);
void start();
void stop();
void interrupt();
void goon();
int get_status();
private:
std::string plugin_name;
std::atomic_int status;
}
}
源文件
#include <pluginlib/class_list_macros.h>
PLUGINLIB_EXPORT_CLASS(general_plugin::MotionControl, general_plugin::BasePlugin)
namespace general_plugin
{
}
CMakeLists.txt文件
cmake_minimum_required(VERSION 3.0.2)
project(motion_control)
add_compile_options(-std=c++11)
find_package(catkin REQUIRED COMPONENTS
base_plugin
pluginlib
roscpp
actionlib
)
catkin_package(
INCLUDE_DIRS include
LIBRARIES motion_control
CATKIN_DEPENDS base_plugin pluginlib roscpp message_generation message_runtime actionlib
# DEPENDS system_lib
)
include_directories(
include
${catkin_INCLUDE_DIRS}
)
add_library(${PROJECT_NAME}
src/motion_control.cpp
)
add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(${PROJECT_NAME}
${catkin_LIBRARIES}
)
add_executable(${PROJECT_NAME}_node test/node.cpp)
target_link_libraries(${PROJECT_NAME}_node
${catkin_LIBRARIES}
${PROJECT_NAME}
)
package.xml文件
<?xml version="1.0"?>
<package format="2">
<name>motion_control</name>
<version>0.0.0</version>
<description>The motion_control package</description>
<maintainer email="starlab@todo.todo">starlab</maintainer>
<license>TODO</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>base_plugin</build_depend>
<build_depend>pluginlib</build_depend>
<build_depend>roscpp</build_depend>
<build_depend>actionlib</build_depend>
<build_export_depend>base_plugin</build_export_depend>
<build_export_depend>pluginlib</build_export_depend>
<build_export_depend>roscpp</build_export_depend>
<exec_depend>base_plugin</exec_depend>
<exec_depend>pluginlib</exec_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>actionlib</exec_depend>
<export>
<base_plugin plugin="${prefix}/motion_control.xml" />
</export>
</package>
motion_control.xml文件(放在功能包里,和package.xml同级目录)
<library path="lib/libmotion_control">
<class name="general_plugin/MotionControl" type="general_plugin::MotionControl" base_class_type="general_plugin::BasePlugin">
<description>
A plugin demo.
</description>
</class>
</library>
【查询插件】
上面例子中,我们的插件隶属于 general_plugin ,所以可以这样查询
rospack plugins --attrib=plugin general_plugin
【使用插件】
通过下面的方式,即可在主程序中动态加载插件类
auto plugin_loader = new pluginlib::ClassLoader<general_plugin::BasePlugin>("base_plugin", "general_plugin::BasePlugin");
boost::shared_ptr<general_plugin::BasePlugin> derive_plugin = plugin_loader->createInstance("general_plugin/MotionControl");
【友情提示】
一般来说,我们可以自己写一个软件总线来管理调度所有的插件,每个功能插件应该是独立的,可控的,并且实现了启停等基本API,切记不可在插件构造函数中写能够造成阻塞的代码段!!