ROS2高效学习第七章 — ros2 plugins编程
ros2 plugins编程
1 前言和资料
本文我们学习 ROS2 的 pluginlib (插件库)的使用技巧。学过 C/C++ 编程的人都知道,代码需要编译成二进制程序才能运行。对于较大的嵌入式项目,比如智驾系统,每改一行代码都要编译,编译后还要部署到开发板,然后才能运行调试,整个过程费时费力。为了解决这种问题,ROS2 提供了插件功能,即 pluginlib C++ 库。基于 pluginlib ,可以把项目代码分为两部分:
一是应用程序,负责定义接口头文件,确定代码框架,并独立编译,不需要包含插件代码。
二是插件程序,负责使用应用程序的头文件,实现灵活设计,并基于应用程序编译。
产物部署后,应用程序可以直接加载插件程序的动态链接库,实现代码调用。有了这套机制,应用程序框架可以设计的非常稳定,插件程序可以实现灵活修改,最终加快了整个项目的集成进度。
本文参考资料如下:
(1)Pluginlib
(2)pluginlib wiki
2 正文
(1)polygon_base 和 polygon_plugins 功能介绍:
polygon_base 包作为应用程序,定义了多边形的接口类,最主要的功能是根据多边形边长,计算多边形面积。
polygon_plugins 包作为插件程序,继承 polygon_base 的多边形接口类,实现了等边三角形和正方形,并重载了面积计算函数。
2.1 polygon_base
(1)创建 polygon_base 和相关文件
cd ~/colcon_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 polygon_base --dependencies pluginlib --node-name area_node
cd polygon_base
mkdir launch
touch launch/area_launch.py
touch src/area_node.cpp include/polygon_base/regular_polygon.hpp
(2)编写 regular_polygon.hpp
#ifndef POLYGON_BASE_REGULAR_POLYGON_HPP
#define POLYGON_BASE_REGULAR_POLYGON_HPP
namespace polygon_base {
// 整个 RegularPolygon 类是一个抽象基类(ABC),由于存在纯虚函数,不能直接实例化,因此也叫接口类
class RegularPolygon {
public:
// = 0: 表示纯虚函数,必须需要在派生(子)类中被实现。
virtual void init(double side_length) = 0;
virtual double area() = 0;
// 虚析构函数。在基类中声明虚析构函数是为了确保当通过基类指针删除派生类对象时能够调用正确的析构函数,从而避免资源泄漏。
virtual ~RegularPolygon(){}
// protected: 表明下面的成员函数和构造函数只能在类内部及继承它的子类中被访问。
protected:
// 受保护的默认构造函数。由于是受保护的,这个构造函数不能被类的外部直接调用,但可以在派生类中被调用。
// 它被定义为受保护是为了防止直接创建 RegularPolygon 类型的对象,因为它是一个抽象基类。
RegularPolygon(){}
};
} // namespace polygon_base
#endif // POLYGON_BASE_REGULAR_POLYGON_HPP
(3)编写 area_node.cpp
#include "pluginlib/class_loader.hpp"
#include "polygon_base/regular_polygon.hpp"
int main(void) {
pluginlib::ClassLoader<polygon_base::RegularPolygon> polygon_loader("polygon_base", "polygon_base::RegularPolygon");
try {
// "polygon_plugins::Square" 和 "polygon_plugins::EquilateralTriangle" 是插件的类名
// 插件的类名信息来自插件的 plugins.xml ,在 polygon_plugins 包里面
// polygon_plugins 使用 pluginlib_export_plugin_description_file(polygon_base plugins.xml) 把插件信息申明出来,运行时加载插件
std::shared_ptr<polygon_base::RegularPolygon> square = polygon_loader.createSharedInstance("polygon_plugins::Square");
square->init(1.0);
printf("sauare area: %fn", square->area());
std::shared_ptr<polygon_base::RegularPolygon> equilateral_triangle = polygon_loader.createSharedInstance("polygon_plugins::EquilateralTriangle");
equilateral_triangle->init(1.0);
printf("equilateral triangle area: %fn", equilateral_triangle->area());
} catch (pluginlib::PluginlibException& ex) {
printf("plugin failed to load: %sn", ex.what());
}
return 0;
}
(4)编写 area_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='polygon_base',
executable='area_node',
name='area_node',
output="screen",
emulate_tty=True
)
])
(5)编写 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_executable(area_node src/area_node.cpp)
target_include_directories(area_node PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include)
ament_target_dependencies(area_node pluginlib)
install(TARGETS
area_node
DESTINATION lib/${PROJECT_NAME})
install(DIRECTORY
launch
DESTINATION share/${PROJECT_NAME}
)
# 必须把接口类的头文件安装出来,不然插件库编译的时候找不到头文件
install(DIRECTORY
include/
DESTINATION include
)
# 接口类头文件必须声明出来,不然插件库编译的时候也找不到头文件
ament_export_include_directories(
include
)
ament_package()
2.2 polygon_plugins
(1)创建 polygon_plugins 包和相关文件
cd ~/colcon_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 polygon_plugins --dependencies polygon_base pluginlib --library-name polygon_plugins
cd polygon_plugins
touch src/polygon_plugins.cpp
touch plugins.xml
(2)编写 polygon_plugins.cpp
#include "polygon_base/regular_polygon.hpp"
#include <cmath>
namespace polygon_plugins {
class Square : public polygon_base::RegularPolygon {
public:
void init(double side_length) override {
side_length_ = side_length;
}
double area() override {
return side_length_ * side_length_;
}
protected:
double side_length_;
};
class EquilateralTriangle : public polygon_base::RegularPolygon {
public:
void init(double side_length) override {
side_length_ = side_length;
}
double getHight() {
// or: side_length_ * sin(M_PI / 3);
return side_length_ * sqrt(3) / 2;
}
double area() override {
// or: side_length_ * side_length_ * sqrt(3) / 4;
return side_length_ * getHight() * 0.5;
}
protected:
double side_length_;
};
} // namespace polygon_plugins
#include "pluginlib/class_list_macros.hpp"
PLUGINLIB_EXPORT_CLASS(polygon_plugins::Square, polygon_base::RegularPolygon)
PLUGINLIB_EXPORT_CLASS(polygon_plugins::EquilateralTriangle, polygon_base::RegularPolygon)
(3)编写 CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(polygon_plugins)
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(ament_cmake_ros REQUIRED)
find_package(polygon_base REQUIRED)
find_package(pluginlib REQUIRED)
# 必须调用,不然polygon_base的area_node样例运行时找不到这个插件,无法实例化
pluginlib_export_plugin_description_file(polygon_base plugins.xml)
add_library(polygon_plugins src/polygon_plugins.cpp)
ament_target_dependencies(polygon_plugins polygon_base pluginlib)
install(TARGETS
polygon_plugins
LIBRARY DESTINATION lib
)
ament_package()
(4)编写 plugins.xml
<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::EquilateralTriangle" base_class_type="polygon_base::RegularPolygon">
<description>this is a EquilateralTriangle plugin </description>
</class>
</library>
2.3 编译并运行
~/colcon_ws
// polygon_base 可以单独编译
colcon build --packages-select polygon_base
// 编译 polygon_plugins 必须依赖polygon_base
colcon build --packages-select polygon_base polygon_plugins
// 运行程序
source install/local_setup.bash
ros2 launch polygon_base area_launch.py
// 将安装目录重命名
~/colcon_ws
mv install bench_test
// 修改 polygon_plugins 代码,并重新编译
colcon build --packages-select polygon_base polygon_plugins
// 替换 bench_test 里面的插件库
cp install/polygon_plugins/lib/libpolygon_plugins.so bench_test/polygon_plugins/lib/
// 重新设置环境变量,并运行例子,两次结果不同
source bench_test/local_setup.bash
ros2 launch polygon_base area_launch.py
3 总结
本文代码托管在本人的github上:polygon_base,polygon_plugins