1 簡介
pluginlib是一個C++庫,可以實現為一個ROS包動態載入和卸載外掛程式。這裡的外掛程式通常是一些功能類,且以運行時可動態載入的庫(如共用對象,動態連結程式庫)的形式存在。藉助pluginlib的協助,使用者不必關心自己的應用程式該如何連結包含自己想使用的的class的庫(如定義class的標頭檔在哪裡,如何定義的),因為pluginlib會在你調用時自動開啟你需要的外掛程式庫(Note:需要提前將外掛程式庫註冊到pluginlib)。使用外掛程式來擴充或者修改應用程式的功能非常方便,不用改動源碼重新編譯應用程式,通過外掛程式的動態載入即可完成功能的擴充和修改。
2 外掛程式編寫
pluginlib利用了C++多態的特性,不同的外掛程式只要使用統一的介面,便可以替換使用。這樣使用者通過調用在外掛程式中實現的統一的介面函數,不需要更改程式,也不需要重新編譯,更換外掛程式即可實現功能修正。
利用pluginlib編寫外掛程式的方法大致包括如下四步: 建立外掛程式基類,定義統一介面(如果為現有介面編寫外掛程式,則跳過該步) 編寫外掛程式類,繼承外掛程式基類,實現統一介面 匯出外掛程式,並編譯為動態庫 將外掛程式加入ROS系統,使其可識別和管理
2.1 建立外掛程式基類
首先,建立工作空間,建立後如下所示
其次,開始編寫外掛程式基類,基類所在標頭檔polygon_base.h放在include/my_plugin_test下即可,
#ifndef POLYGON_BASE_H_#define POLYGON_BASE_H_namespace polygon_base{class Polygon{ public:Polygon() {};virtual ~Polygon() {};virtual void init(float side_len) = 0;virtual float area() = 0;};};#endif
2.2 建立外掛程式類
所外掛程式類的標頭檔polygon_plugin.h在放在include/my_plugin_test目錄下。
#ifndef POLYGON_PLUGIN_H_#define POLYGON_PLUGIN_H_#include <cmath>#include <my_plugin_test/polygon_base.h>namespace polygon_plugin{class Square: public polygon_base::Polygon{public:Square() {};virtual ~Square() {};virtual void init(float side_len){this->side_len = side_len;}virtual float area(){return (side_len * side_len);}private:float side_len;};class Triangle: public polygon_base::Polygon{public:Triangle() {};virtual ~Triangle() {};virtual void init(float side_len){this->side_len = side_len;}virtual float area(){return 0.5 * (side_len * ( sqrt( (side_len * side_len) - (0.5 * side_len)*(0.5 * side_len) ) ) );}private:float side_len;};};#endif
3 匯出外掛程式,並編譯為動態連結程式庫
3.1匯出外掛程式
利用 pluginlib 庫提供的宏操作註冊外掛程式,並且編譯為動態連結程式庫。
在src目錄下添加polygon_plugin.cpp,
#include <pluginlib/class_list_macros.h>#include <my_plugin_test/polygon_base.h>#include <my_plugin_test/polygon_plugin.h> //mark Square and Triangle as the exported class PLUGINLIB_EXPORT_CLASS(polygon_plugin::Triangle, polygon_base::Polygon)PLUGINLIB_EXPORT_CLASS(polygon_plugin::Square, polygon_base::Polygon)
如果要實現class可動態載入,必須要將其標記為可匯出的class。通過特定的宏PLUGINLIB_EXPORT_CLASS可以完成匯出,該宏通常放置於cpp檔案的底部。這個宏第一個參數是外掛程式類全名(含namespace),第二個參數是外掛程式基類全名(含namespace)。
3.2 編譯為動態連結程式庫
要將外掛程式編譯為動態連結程式庫,需要相應修改CMakeLists.txt檔案,添加如下幾行:
include_directories( include ${catkin_INCLUDE_DIRS} ) ## Declare a C++ library add_library(polygon_plugin src/${PROJECT_NAME}/polygon_plugin.cpp )
4 將外掛程式加入ROS系統,使其可識別和管理
4.1 建立外掛程式描述檔案
外掛程式描述檔案是一個XML格式的檔案,用於儲存外掛程式的重要訊息(如,外掛程式庫路徑,外掛程式名稱,外掛程式類類型,外掛程式基類類型)。
我們在my_plugin_test目錄下,建立名為polygon_plugin.xml的檔案,
<library path="lib/libpolygon_plugin"> <class type="polygon_plugin::Triangle" base_class_type="polygon_base::Polygon"> <description>This is a triangle plugin.</description> </class> <class type="polygon_plugin::Square" base_class_type="polygon_base::Polygon"> <description>This is a square plugin.</description> </class></library>
這裡標籤library和其屬性path一起定義了主package相對於外掛程式庫的路徑,一個外掛程式庫可以包含多個不同的外掛程式類(如這裡是2個外掛程式類)。
這裡的標籤class用以描述外掛程式庫中的外掛程式類,屬性type指定外掛程式類的類型(必須全名),屬性base_class_type指定外掛程式基類的類型(必須全名),屬性description描述外掛程式類的功能。
注意:外掛程式描述檔案還有一個標籤class_libraries這裡沒有使用,其可以實現在一個外掛程式描述檔案包含多個庫,該標籤無屬性。
4.2 註冊外掛程式到ROS系統
為確保pluginlib可以查到ROS系統所有外掛程式,定義外掛程式的package必須顯式的指定哪個包匯出了什麼外掛程式。
這通常在package.xml檔案中定義,
<export> <my_plugin_test plugin="${prefix}/polygon_plugin.xml"></export>
這裡標籤my_plugin_test是定義外掛程式基類的package名稱,屬性plugin是前面定義的外掛程式描述符檔案。
注意:如果外掛程式類與基類不在同一package,為了使外掛程式的export生效,還必須添加對外掛程式基類所在package的依賴。
<build_depend>my_plugin_test</build_depend> <run_depend>my_plugin_test</run_depend>
5 check外掛程式是否在ROS下可以查看
在catkin_make執行成功之後,source develop/setup.bash,然後運行如下命令如果能正確看到輸出polygon_plugin.xml則ok。
rospack plugins --attrib=plugin my_plugin_test
6 調用外掛程式
6.1 在src目錄下建立my_plugin_loader.cpp
#include <ros/ros.h>#include <pluginlib/class_loader.h>#include <my_plugin_test/polygon_base.h>int main(int argc, char ** argv){ros::init(argc, argv, "my_plugin_loader");ros::NodeHandle nh; float side_len = 5.0; std::string param_name = "polygon_plugin";std::string plugin_class;if(!nh.getParam(param_name.c_str(), plugin_class)){ROS_ERROR("can't get param");return 0;}/** * Define plugin loader object(polygon_loader) for loading my plugin. * param1: the path of plugin package, param2:the base class of plugin class with full name */pluginlib::ClassLoader<polygon_base::Polygon> polygon_loader("my_plugin_test", "polygon_base::Polygon"); try{/*Based on input param to create the corresponding plugin instance by ClassLoader*/boost::shared_ptr<polygon_base::Polygon> polygon_cal = polygon_loader.createInstance(plugin_class);polygon_cal->init(side_len);ROS_INFO("plugin class is %s, area is %f",plugin_class.c_str(), polygon_cal->area());} /*catch exception ClassLaoder object(polygon_loader) exception*/catch(pluginlib::PluginlibException& ex){ROS_ERROR("The plugin failed to load for some reason. Error: %s", ex.what());}return 0;}
6.2 修改CMakeLists.txt,添加
## Declare a C++ executableadd_executable(my_plugin_loader src/my_plugin_loader.cpp)## Add cmake target dependencies of the executable## same as for the library above# add_dependencies(my_plugin_test_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})## Specify libraries to link a library or executable target againsttarget_link_libraries(my_plugin_loader ${catkin_LIBRARIES})
6.3 建立開機檔案
在my_plugin_test目錄下建立launch檔案夾添加檔案my_plugin_loader.launch
<launch><!--plugin select--><!--<param name="polygon_plugin" value="polygon_plugin::Square"/> --><param name="polygon_plugin" value="polygon_plugin::Triangle"/><node name="my_plugin_loader" pkg="my_plugin_test" type="my_plugin_loader" output="screen"/></launch>
最後目錄工作空間目錄結構如下所示:
6.4 測試
方式一:通過開機檔案
roslaunch my_plugin_test my_plugin_loader.launch
輸出:
... logging to /home/siriansu/.ros/log/f05ccba8-a4a7-11e6-9012-001fc69be782/roslaunch-siriansu-nuc-24328.log
Checking log directory for disk usage. This may take awhile.
Press Ctrl-C to interrupt
Done checking log file disk usage. Usage is <1GB.
started roslaunch server http://siriansu-nuc:40746/
SUMMARY
========
PARAMETERS
* /polygon_plugin: polygon_plugin::T...
* /rosdistro: kinetic
* /rosversion: 1.12.5
NODES
/
my_plugin_loader (my_plugin_test/my_plugin_loader)
ROS_MASTER_URI=http://localhost:11311
core service [/rosout] found
process[my_plugin_loader-1]: started with pid [24346]
[ INFO] [1478842570.904729245]: plugin class is polygon_plugin::Triangle, area is 10.825317
[my_plugin_loader-1] process has finished cleanly
log file: /home/siriansu/.ros/log/f05ccba8-a4a7-11e6-9012-001fc69be782/my_plugin_loader-1*.log
all processes on machine have died, roslaunch will exit
shutting down processing monitor...
... shutting down processing monitor complete
done
方式二:命令列方式
rosparam set polygon_plugin polygon_plugin::Square
rosrun my_plugin_test my_plugin_loader
輸出:
[ INFO] [1478842687.012935017]: plugin class ispolygon_plugin::Square, area is25.000000
rosparam set polygon_plugin polygon_plugin::Triangle
rosrun my_plugin_test my_plugin_loader
輸出:
[ INFO] [1478844657.917205466]: plugin class ispolygon_plugin::Triangle, area is10.825317