ROS1 Nodelets 与 ROS2 rclcpp_components 多节点运行以及功能插件
0. 概述
在ROS1中,Nodelets和Nodes是两种不同的实现方法。Nodelets允许在同一进程中运行多个节点,从而减少了跨进程通信的开销,提高了性能。而在ROS2中,推荐使用组件(Components)来实现类似的功能,组件通过统一的API实现了更好的模块化与可扩展性。之前我们在ROS到ROS2的多节点组合运行对这两个内容进行了整理归纳。这里我们更进一步系统性的介绍Nodelets 与 组件的比较。并用最简单的实例来阐述这两个是如何使用的。
特性 | Nodelets (ROS1) | 组件 (ROS2) |
---|---|---|
运行方式 | 通过Nodelet Manager在同一进程中运行 | 通过component_manager动态加载和卸载 |
API 一致性 | 不一致,Node与Nodelet使用不同的API | 统一API,简化了编程模型 |
生命周期管理 | 需要手动管理,复杂 | 通过rclcpp 提供的生命周期管理 |
动态加载卸载 | 通过Nodelet Manager实现 | 通过component_manager实现 |
性能 | 降低了跨进程通信开销 | 进一步优化了通信效率 |
1. ROS1 Nodelets
Nodelets是ROS1中的一种特殊机制,允许在同一进程中运行多个节点,以减少跨进程通信的开销。这种设计使得Nodelets能够共享内存,从而提高性能,特别是在需要频繁通信的情况下,如图像处理和传感器数据处理。Nodelet通过Nodelet Manager进行管理,后者负责加载、卸载和管理Nodelet的生命周期。
1.1 Nodelet 概述
Nodelet的主要优势包括:
- 高效的通信:由于所有Nodelet都运行在同一个进程内,因此它们之间的通信开销显著降低。
- 共享内存:Nodelet可以直接访问彼此的内存空间,支持更快速的数据交换。
- 动态加载:Nodelet可以在运行时动态加载和卸载,增强系统的灵活性。
1.2 创建 Nodelet
1.2.1 定义 Nodelet 类
下面是一个简单的Nodelet示例,它每秒发布一条消息到特定的主题。
#include <nodelet/nodelet.h>
#include <ros/ros.h>
#include <std_msgs/String.h>namespace my_namespace {
class MyNodelet : public nodelet::Nodelet {
public:virtual void onInit() override {ros::NodeHandle& nh = getNodeHandle(); // 获取NodeHandlepub_ = nh.advertise<std_msgs::String>("topic_name", 1); // 创建发布者timer_ = nh.createTimer(ros::Duration(1.0), &MyNodelet::timerCallback, this); // 创建定时器}private:void timerCallback(const ros::TimerEvent&) {std_msgs::String msg;msg.data = "Hello from Nodelet!"; // 发布的消息内容pub_.publish(msg); // 发布消息}ros::Publisher pub_; // 发布者对象ros::Timer timer_; // 定时器对象
};} // namespace my_namespace#include <pluginlib/class_list_macros.h>
PLUGINLIB_EXPORT_CLASS(my_namespace::MyNodelet, nodelet::Nodelet) // 注册Nodelet
1.2.2 CMakeLists.txt 配置
在CMakeLists.txt中配置Nodelet的构建设置:
cmake_minimum_required(VERSION 3.0.2)
project(my_nodelet)find_package(catkin REQUIRED COMPONENTSnodeletroscppstd_msgs
)include_directories(${catkin_INCLUDE_DIRS}
)add_library(my_nodelet src/my_nodelet.cpp) // 添加Nodelet库
target_link_libraries(my_nodelet ${catkin_LIBRARIES}) // 链接依赖库add_dependencies(my_nodelet ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) // 依赖配置install(TARGETS my_nodeletLIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} // 安装库
)
1.2.3 package.xml 配置
在package.xml文件中声明Nodelet的依赖关系:
<package format="2"><name>my_nodelet</name><version>0.0.0</version><description>The my_nodelet package</description><buildtool_depend>catkin</buildtool_depend><depend>nodelet</depend><depend>roscpp</depend><depend>std_msgs</depend><export></export>
</package>
1.2.4 启动 Nodelet
在launch文件中启动Nodelet及其管理器:
<launch><node pkg="nodelet" type="nodelet" name="my_nodelet_manager" args="manager" /> <!-- 启动Nodelet管理器 --><node pkg="nodelet" type="nodelet" name="my_nodelet" args="load my_namespace/MyNodelet my_nodelet_manager" /> <!-- 加载Nodelet -->
</launch>
1.3 调用 Nodelet
使用以下命令启动Nodelet:
roslaunch my_nodelet my_nodelet.launch
ROS 2 rclcpp_components
2.1 组件概述
在ROS 2中,rclcpp_components
提供了一种高效的机制,类似于ROS 1中的Nodelet,允许开发者将节点以组件的形式组织。这种方式支持动态加载和卸载组件,从而实现高效的资源利用和模块化设计。组件可以在同一进程内运行,从而减少开销并提高通信效率。
2.2 创建组件
2.2.1 定义组件类
组件类通常继承自rclcpp::Node
,并在构造函数中初始化所需的资源。
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"class MyComponentNode : public rclcpp::Node {
public:// 构造函数,接受NodeOptions作为参数MyComponentNode(const rclcpp::NodeOptions & options): Node("my_component_node", options) {// 创建发布者,发布在"topic_name"主题上publisher_ = this->create_publisher<std_msgs::msg::String>("topic_name", 10);// 创建定时器,每秒调用一次timer_callbacktimer_ = this->create_wall_timer(std::chrono::seconds(1),[this]() { this->timer_callback(); });}private:// 定时器回调函数,用于发布消息void timer_callback() {auto message = std::make_shared<std_msgs::msg::String>();message->data = "Hello from Component!";publisher_->publish(*message); // 发布消息}// 成员变量:发布者和定时器的智能指针rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;rclcpp::TimerBase::SharedPtr timer_;
};// 使用宏注册组件,使其可被动态加载
#include "rclcpp_components/register_node_macro.hpp"
RCLCPP_COMPONENTS_REGISTER_NODE(MyComponentNode)
2.2.2 CMakeLists.txt 配置
为了正确编译和链接组件,需要在CMakeLists.txt文件中进行适当配置。
cmake_minimum_required(VERSION 3.5)
project(my_component)find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(rclcpp_components REQUIRED)
find_package(std_msgs REQUIRED)# 添加共享库
add_library(my_component SHAREDsrc/my_component.cpp
)# 设置依赖
ament_target_dependencies(my_component"rclcpp""rclcpp_components""std_msgs"
)# 注册组件
rclcpp_components_register_node(my_componentPLUGIN "MyComponentNode"EXECUTABLE my_component_node
)# 安装目标
install(TARGETS my_componentEXPORT export_my_componentLIBRARY DESTINATION lib
)
2.2.3 package.xml 配置
在package.xml
中声明依赖关系。
<package format="2"><name>my_component</name><version>0.0.0</version><description>The my_component package</description><buildtool_depend>ament_cmake</buildtool_depend><depend>rclcpp</depend><depend>rclcpp_components</depend><depend>std_msgs</depend><export></export>
</package>
2.2.4 创建 Launch 文件
Launch文件用于启动组件并配置其参数。
from launch import LaunchDescription
from launch_ros.actions import ComposableNodeContainer, ComposableNodedef generate_launch_description():container = ComposableNodeContainer(name='my_container',namespace='',package='rclcpp_components',executable='component_container',composable_node_descriptions=[ComposableNode(package='my_component',plugin='MyComponentNode',name='my_component',parameters=[{'param_name': 'param_value'}])],output='both',)return LaunchDescription([container])
2.3 调用组件
在终端中运行以下命令启动组件:
ros2 launch my_component my_component_launch.py
2.4 动态加载和卸载组件
ros2 component
命令允许在运行时动态加载和卸载组件。
2.4.1 加载组件
ros2 component load /ComponentManager my_component MyComponentNode
2.4.2 卸载组件
ros2 component unload /ComponentManager <component_name>
在ROS(Robot Operating System)中,插件是一种可扩展的机制,允许开发者以动态的方式加载和使用软件组件。ROS1和ROS2都支持插件,但它们的实现方式有所不同。下面将分别介绍ROS1和ROS2插件的操作及示例代码。
好的!下面是一个更为详细的ROS1和ROS2插件实例,包括CMakeLists.txt文件的配置,确保代码完整且可运行。
3. ROS1 插件示例
3.1 创建插件接口
首先,定义一个插件接口 MyPluginInterface
,这个接口包含一个虚函数 printMessage
,用于输出消息。
// my_plugin_interface.h
#ifndef MY_PLUGIN_INTERFACE_H
#define MY_PLUGIN_INTERFACE_H#include <string>// 定义插件接口
class MyPluginInterface
{
public:virtual ~MyPluginInterface() {} // 虚析构函数virtual void printMessage(const std::string &message) = 0; // 纯虚函数
};#endif // MY_PLUGIN_INTERFACE_H
3.2 实现插件
接下来,实现该接口的插件,命名为 MyPlugin
,并实现 printMessage
函数。
// my_plugin.cpp
#include "my_plugin_interface.h"
#include <pluginlib/class_list_macros.h>
#include <iostream>class MyPlugin : public MyPluginInterface
{
public:// 实现 printMessage 函数void printMessage(const std::string &message) override{std::cout << "Plugin message: " << message << std::endl; // 打印消息}
};// 使用 pluginlib 注册插件
PLUGINLIB_EXPORT_CLASS(MyPlugin, MyPluginInterface)
3.3 主程序
在主程序中加载和使用插件。
// main.cpp
#include <ros/ros.h>
#include <pluginlib/class_loader.h>
#include "my_plugin_interface.h"int main(int argc, char **argv)
{ros::init(argc, argv, "plugin_example"); // 初始化 ROS 节点ros::NodeHandle nh;// 创建插件加载器pluginlib::ClassLoader<MyPluginInterface> loader("my_plugin_package", "MyPluginInterface");try{// 创建插件实例boost::shared_ptr<MyPluginInterface> plugin_instance = loader.createInstance("MyPlugin");plugin_instance->printMessage("Hello from the plugin!"); // 调用插件方法}catch (const pluginlib::PluginlibException &ex){ROS_ERROR("Pluginlib exception: %s", ex.what()); // 捕捉异常并输出错误}ros::spin(); // 保持节点运行return 0;
}
3.4 CMakeLists.txt
构建项目的 CMake 配置文件。
cmake_minimum_required(VERSION 3.0.2)
project(my_plugin_package)find_package(catkin REQUIRED COMPONENTSroscpppluginlib
)include_directories(${catkin_INCLUDE_DIRS} // 包含目录
)add_library(my_plugin src/my_plugin.cpp) // 添加插件库
add_dependencies(my_plugin ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})install(TARGETS my_pluginEXPORT my_plugin_packageLIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} // 安装库
)install(FILES my_plugin_interface.hDESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} // 安装头文件
)// 安装资源文件(如有)
install(DIRECTORY resource/DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/resource
)// 导出插件描述文件
pluginlib_export_plugin_description_file(my_plugin_package my_plugin_plugins.xml)
3.5 插件描述文件
创建插件描述文件 my_plugin_plugins.xml
,用于指定插件信息。
<?xml version="1.0"?>
<library path="my_plugin"><class name="MyPlugin" type="MyPlugin" base_class_type="MyPluginInterface"><description>A simple plugin that prints messages.</description></class>
</library>