ROS学习 | ROS1 Nodelets 与 ROS2 rclcpp_components 多节点运行以及功能插件

科技   2024-11-13 17:20   湖北  

0. 概述


在ROS1中,Nodelets和Nodes是两种不同的实现方法。Nodelets允许在同一进程中运行多个节点,从而减少了跨进程通信的开销,提高了性能。而在ROS2中,推荐使用组件(Components)来实现类似的功能,组件通过统一的API实现了更好的模块化与可扩展性。之前我们在ROS到ROS2的多节点组合运行(https://www.guyuehome.com/detail?id=1825485699164241921)对这两个内容进行了整理归纳。这里我们更进一步系统性的介绍Nodelets 与 组件的比较。并用最简单的实例来阐述这两个是如何使用的。




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(); // 获取NodeHandle        pub_ = 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 COMPONENTS  nodelet  roscpp  std_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_nodelet  LIBRARY 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_callback        timer_ = 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 SHARED  src/my_component.cpp)
# 设置依赖ament_target_dependencies(my_component  "rclcpp"  "rclcpp_components"  "std_msgs")
# 注册组件rclcpp_components_register_node(my_component  PLUGIN "MyComponentNode"  EXECUTABLE my_component_node)
# 安装目标install(TARGETS my_component  EXPORT export_my_component  LIBRARY 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 LaunchDescriptionfrom launch_ros.actions import ComposableNodeContainer, ComposableNode
def 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 COMPONENTS  roscpp  pluginlib)
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_plugin  EXPORT my_plugin_package  LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} // 安装库)
install(FILES my_plugin_interface.h  DESTINATION ${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>



4. ROS2 插件示例


4.1 创建插件接口


创建插件接口 MyPluginInterface,类似于 ROS1。


// my_plugin_interface.hpp#ifndef MY_PLUGIN_INTERFACE_HPP#define MY_PLUGIN_INTERFACE_HPP
#include <string>
class MyPluginInterface{public:    virtual ~MyPluginInterface() {} // 虚析构函数    virtual void printMessage(const std::string &message) = 0; // 纯虚函数};
#endif // MY_PLUGIN_INTERFACE_HPP


4.2 实现插件


实现 MyPlugin 插件。


// my_plugin.cpp#include "my_plugin_interface.hpp"#include <pluginlib/class_list_macros.hpp>#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)


4.3 主程序


在主程序中加载和使用插件。


// main.cpp#include <rclcpp/rclcpp.hpp>#include <pluginlib/class_loader.hpp>#include "my_plugin_interface.hpp"
int main(int argc, char **argv){    rclcpp::init(argc, argv); // 初始化 ROS2
   // 创建插件加载器    pluginlib::ClassLoader<MyPluginInterface> loader("my_plugin_package", "MyPluginInterface");
   try    {        // 创建插件实例        auto plugin_instance = loader.createSharedInstance("MyPlugin");        plugin_instance->printMessage("Hello from the plugin!"); // 调用插件方法    }    catch (const pluginlib::PluginlibException &ex)    {        RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Pluginlib exception: %s", ex.what()); // 捕捉异常并输出错误    }
   rclcpp::shutdown(); // 关闭 ROS2    return 0;}


4.4 CMakeLists.txt


构建项目的 CMake 配置文件。


cmake_minimum_required(VERSION 3.5)project(my_plugin_package)
find_package(ament_cmake REQUIRED) // 查找 ament_cmake 包find_package(rclcpp REQUIRED) // 查找 rclcpp 包find_package(pluginlib REQUIRED) // 查找 pluginlib 包
include_directories(include)
add_library(my_plugin SHARED  src/my_plugin.cpp // 添加插件库)
ament_target_dependencies(my_plugin  rclcpp  pluginlib)
// 导出插件描述文件pluginlib_export_plugin_description_file(my_plugin_package my_plugin_plugins.xml)
install(TARGETS my_plugin  DESTINATION lib/${PROJECT_NAME} // 安装库)
install(DIRECTORY include/  DESTINATION include // 安装头文件)
ament_package() // 完成包的构建


4.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>



5. 参考链接


https://www.guyuehome.com/detail?id=1825485699164241921


https://blog.51cto.com/u_14568336/10659762


https://www.cnblogs.com/_bob/p/18165425


https://wenku.csdn.net/answer/50567c95c5cb49059f471019c24b612b


https://blog.csdn.net/weixin_41680653/article/details/114638257



讲师招募


招募要求

完成符合要求的机器人相关视频制作

总时长需达到 3小时以上

视频内容需为精品课程,确保高质量和专业性


讲师奖励

享受课程收入分成

赠送 2门 古月学院在售精品课程(训练营除外)


联系我们

添加工作人员微信:GYH-xiaogu





古月居
专业的ROS机器人知识社区和产业服务平台
 最新文章