ros2 跟着官方教学从零开始 插件
ros2 从零开始14 插件
前言
背景
本教程源自http://wiki.ros.org/pluginlib
pluginlib 是C++ 库, ROS 包内加载和卸载插件的。 插件是可动态加载的类,从运行时库(即共享对象、动态链接库)加载出来。 使用 pluginlib,你不必显式地将应用与包含类的库关联——而是可以在任何时刻打开包含导出类的库。 插件对于扩展/修改应用行为非常有用,无需应用源代码。
简单来说,pluginlib是ros节点运行时动态加载上的。有点类似于平时我们用dl_open来动态加载,只不过ros2把它封装了,使用比较方便些。
在我们的工程高度抽象时,我们更新pluginlib库,可以在不动基础代码的情况下,就可以完成功能升级。
实践
1.创建一个包
进入工作区的src目录,用如下指令创建我们的基类包
ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies pluginlib --node-name area_node polygon_base
polygon_base 是我们的本次的包名,area_node是它里面的一个节点。
root@bc2bf85b2e4a:~/ros2_ws# cd ~/ros2_ws/src/
root@bc2bf85b2e4a:~/ros2_ws/src# ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies pluginlib --node-name area_node polygon_base
going to create a new package
package name: polygon_base
destination directory: /root/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['root <****@****>']
licenses: ['Apache-2.0']
build type: ament_cmake
dependencies: ['pluginlib']
node_name: area_node
creating folder ./polygon_base
creating ./polygon_base/package.xml
creating source and include folder
creating folder ./polygon_base/src
creating folder ./polygon_base/include/polygon_base
creating ./polygon_base/CMakeLists.txt
creating ./polygon_base/src/area_node.cpp
2. 创建pluginlib库
2.1 新增基类头文件
在polygon_base/include/polygon_base/目录新建文件regular_polygon.hpp,并且内容添加如下
#ifndef POLYGON_BASE_REGULAR_POLYGON_HPP
#define POLYGON_BASE_REGULAR_POLYGON_HPP
namespace polygon_base
{
class RegularPolygon
{
public:
virtual void initialize(double side_length) = 0;
virtual double area() = 0;
virtual ~RegularPolygon(){}
protected:
RegularPolygon(){}
};
} // namespace polygon_base
#endif // POLYGON_BASE_REGULAR_POLYGON_HPP
以上代码定义一个命名空间是polygon_base的基类RegularPolygon, 基类有个虚方法initialize用于初始化。同时有个无参构造函数RegularPolygon(){}, 无参构造函数是pluginlib要求的。如果想要给对象传替参数,就调用initialize即可。
2.2 修改CMakeLists.txt
我们依赖pluginlib,CMakeLists.txt理应有find_package(pluginlib REQUIRED)选项(如果在创建包时没有–dependencies pluginlib,则需要手动添加),在find_package(pluginlib REQUIRED)之后,添加如下内容
# 创建接口库和别名
add_library(${PROJECT_NAME} INTERFACE)
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
# 设置编译标准
target_compile_features(${PROJECT_NAME} INTERFACE c_std_99 cxx_std_17)
# 设置头文件包含路径(使用生成器表达式)
target_include_directories(${PROJECT_NAME} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include/${PROJECT_NAME}>
)
# 链接依赖库
target_link_libraries(${PROJECT_NAME} INTERFACE ${pluginlib_TARGETS})
# 安装头文件
install(DIRECTORY include/
DESTINATION include/${PROJECT_NAME}
)
# 安装库并导出目标
install(TARGETS ${PROJECT_NAME}
EXPORT export_${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
# 安装导出文件
install(EXPORT export_${PROJECT_NAME}
NAMESPACE ${PROJECT_NAME}::
DESTINATION share/${PROJECT_NAME}/cmake
)
同时,在ament_package 之前添加如下内容:
# 导出旧式 CMake 变量
ament_export_include_directories(
include
)
# 导出现代 CMake 目标
ament_export_targets(
export_${PROJECT_NAME}
)
完整的CMakeLists.txt如下:
cmake_minimum_required(VERSION 3.8)
project(polygon_base)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(pluginlib REQUIRED)
# 创建接口库和别名
add_library(${PROJECT_NAME} INTERFACE)
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
# 设置编译标准
target_compile_features(${PROJECT_NAME} INTERFACE c_std_99 cxx_std_17)
# 设置头文件包含路径(使用生成器表达式)
target_include_directories(${PROJECT_NAME} INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include/${PROJECT_NAME}>
)
# 链接依赖库
target_link_libraries(${PROJECT_NAME} INTERFACE ${pluginlib_TARGETS})
# 安装头文件
install(DIRECTORY include/
DESTINATION include/${PROJECT_NAME}
)
# 安装库并导出目标
install(TARGETS ${PROJECT_NAME}
EXPORT export_${PROJECT_NAME}
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin
)
# 安装导出文件
install(EXPORT export_${PROJECT_NAME}
NAMESPACE ${PROJECT_NAME}::
DESTINATION share/${PROJECT_NAME}/cmake
)
add_executable(area_node src/area_node.cpp)
target_include_directories(area_node PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
target_compile_features(area_node PUBLIC c_std_99 cxx_std_17) # Require C99 and C++17
ament_target_dependencies(
area_node
"pluginlib"
)
install(TARGETS area_node
DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
# 导出旧式 CMake 变量
ament_export_include_directories(
include
)
# 导出现代 CMake 目标
ament_export_targets(
export_${PROJECT_NAME}
)
ament_package()
3. 实现抽象类(实现插件具体代码)
现在我们将写抽象类的两个非虚实现。 在文件夹src里用以下命令创建第二个包ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies polygon_base pluginlib --library-name polygon_plugins polygon_plugins,注意看,我们第二个包polygon_plugins依赖于包polygon_base和pluginlib
执行如下:
root@bc2bf85b2e4a:~/ros2_ws/src# ros2 pkg create --build-type ament_cmake --license Apache-2.0 --dependencies polygon_base pluginlib --library-name polygon_plugins polygon_plugins
going to create a new package
package name: polygon_plugins
destination directory: /root/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['root <****@****>']
licenses: ['Apache-2.0']
build type: ament_cmake
dependencies: ['polygon_base', 'pluginlib']
library_name: polygon_plugins
creating folder ./polygon_plugins
creating ./polygon_plugins/package.xml
creating source and include folder
creating folder ./polygon_plugins/src
creating folder ./polygon_plugins/include/polygon_plugins
creating ./polygon_plugins/CMakeLists.txt
creating ./polygon_plugins/include/polygon_plugins/polygon_plugins.hpp
creating ./polygon_plugins/src/polygon_plugins.cpp
creating ./polygon_plugins/include/polygon_plugins/visibility_control.h
3.1 修改实现
打开进行编辑文件polygon_plugins/src/polygon_plugins.cpp,并粘贴以下内容:
#include <polygon_base/regular_polygon.hpp>
#include <cmath>
namespace polygon_plugins
{
class Square : public polygon_base::RegularPolygon
{
public:
void initialize(double side_length) override
{
side_length_ = side_length;
}
double area() override
{
return side_length_ * side_length_;
}
protected:
double side_length_;
};
class Triangle : public polygon_base::RegularPolygon
{
public:
void initialize(double side_length) override
{
side_length_ = side_length;
}
double area() override
{
return 0.5 * side_length_ * getHeight();
}
double getHeight()
{
return sqrt((side_length_ * side_length_) - ((side_length_ / 2) * (side_length_ / 2)));
}
protected:
double side_length_;
};
}
#include <pluginlib/class_list_macros.hpp>
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Triangle, polygon_base::RegularPolygon)
解析:
正方形和三角形的实现相当简单:保存边长,并用它来计算面积。 跟插件库的有关的部分是最后三行,调用了一些神奇的宏PLUGINLIB_EXPORT_CLASS,将类注册为真正的插件。PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
- class_type-插件类的全限定类型,在此例中为
polygon_plugins::Square和polygon_plugins::Triangle - base_class_type-基类的全限定类型,在此例中为
polygon_base::RegularPolygon
3.2 新增plugins.xml
上述步骤允许在加载包含库时创建插件实例,但插件加载器仍需找到该库并知道在库中引用的内容。 为此,我们新增plugins.xml文件(在polygon_plugins包目录下),配合包清单中的特殊导出行,使插件的所有必要信息都能在 ROS 工具链中获得。
<library path="polygon_plugins">
<class type="polygon_plugins::Square" base_class_type="polygon_base::RegularPolygon">
<description>This is a square plugin.</description>
</class>
<class type="polygon_plugins::Triangle" base_class_type="polygon_base::RegularPolygon" name="awesome_triangle">
<description>This is a triangle plugin.</description>
</class>
</library>
有几点需要注意:
标签library给出了包含我们想导出插件的库的相对路径。 在ROS2中,那只是包的名字。 但是在ROS1中,它包含前缀lib或者lib/libname(例如:lib/libpolygon_plugins),但在这里更简单。
标签class 声明了一个我们想从库导出的插件。 让我们来看看它的参数:
- type:插件的完全限定类型。 对我们来说,就是
polygon_plugins::Square。 - base_class:插件的完全限定基类类型。 对我们来说,就是
polygon_base::RegularPolygon。 - description: 插件及其功能描述。
- name(可选):类加载器使用的查找名称。
3.3 修改CMakeLists.txt
最后一步是通过CMakeLists.txt导出你的插件。在你的行find_package(pluginlib REQUIRED)后加上以下一句:
pluginlib_export_plugin_description_file(polygon_base plugins.xml)
命令pluginlib_export_plugin_description_file的参数如下:
- 带有基类的包,本例是polygon_base
- 插件声明XML的相对路径,本例是plugins.xml
4. 使用插件
现在我们可以使用插件了,可以在任意的包中使用,但是本例我们在之前的基类包polygon_base中使用,编辑文件polygon_base/src/area_node.cpp,将如下内容贴上:
#include <pluginlib/class_loader.hpp>
#include <polygon_base/regular_polygon.hpp>
int main(int argc, char** argv)
{
// To avoid unused parameter warnings
(void) argc;
(void) argv;
pluginlib::ClassLoader<polygon_base::RegularPolygon> poly_loader("polygon_base", "polygon_base::RegularPolygon");
try
{
std::shared_ptr<polygon_base::RegularPolygon> triangle = poly_loader.createSharedInstance("awesome_triangle");
triangle->initialize(10.0);
std::shared_ptr<polygon_base::RegularPolygon> square = poly_loader.createSharedInstance("polygon_plugins::Square");
square->initialize(10.0);
printf("Triangle area: %.2f\n", triangle->area());
printf("Square area: %.2f\n", square->area());
}
catch(pluginlib::PluginlibException& ex)
{
printf("The plugin failed to load for some reason. Error: %s\n", ex.what());
}
return 0;
}
ClassLoader是需要理解的关键类,定义在头文件pluginlib/class_loader.hpp中:
- 它采用了基类模板
polygon_base::RegularPolygon。 - 第一个参数是基类包名的字符串
polygon_base - 第二个参数是一个带有插件完全限定基类类型的字符串
polygon_base::RegularPolygon(命名空间+基类名)。
有多种方式可以实例化该类的实例。 在这个例子中,我们调用poly_loader.createSharedInstance("awesome_triangle"); 和 poly_loader.createSharedInstance("polygon_plugins::Square"); 。入参可以是插件类的全限定类型type(声明XML文件的属性,本例是polygon_plugins::Square),也可以是可选的名称name(声明XML文件的属性,本例是awesome_triangle)。
5. 编译
进入工作区,用colcon build ,等待编译完成。
root@bc2bf85b2e4a:~/ros2_ws# colcon build --packages-select polygon_base polygon_plugins
Starting >>> polygon_base
Finished <<< polygon_base [0.11s]
Starting >>> polygon_plugins
Finished <<< polygon_plugins [0.71s]
Summary: 2 packages finished [0.98s]
root@bc2bf85b2e4a:~/ros2_ws#
6. 运行查看
编译完成后,我们需要安装后才能运行,使用
source install/setup.sh
使用ros2 plugin list查看插件:
root@bc2bf85b2e4a:~/ros2_ws# ros2 plugin list
polygon_plugins:
Plugin(name='polygon_plugins::Square', type='polygon_plugins::Square', base='polygon_base::RegularPolygon')
Plugin(name='awesome_triangle', type='polygon_plugins::Triangle', base='polygon_base::RegularPolygon')
接下来,输入如下命令ros2 run polygon_base area_node,运行节点, 如下:
root@bc2bf85b2e4a:~/ros2_ws# ros2 run polygon_base area_node
Triangle area: 43.30
Square area: 100.00
可以看到,节点正常运行,并正确计算了面积。
总结
完成了插件的介绍,之后就是中级教程了。中级教程会涉及到动作actions ,敬请期待。
更多推荐
所有评论(0)