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

0. 概述

在ROS1中,Nodelets和Nodes是两种不同的实现方法。Nodelets允许在同一进程中运行多个节点,从而减少了跨进程通信的开销,提高了性能。而在ROS2中,推荐使用组件(Components)来实现类似的功能,组件通过统一的API实现了更好的模块化与可扩展性。之前我们在ROS到ROS2的多节点组合运行对这两个内容进行了整理归纳。这里我们更进一步系统性的介绍Nodelets 与 组件的比较。并用最简单的实例来阐述这两个是如何使用的。(表格来源:古月居 - ROS机器人知识分享社区

特性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(); // 获取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

2.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 LaunchDescription
from 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. 参考链接

古月居 - ROS机器人知识分享社区

rclcpp_components_register_node_kelukelu1的技术博客_51CTO博客

编写ROS2的组件(Component) - _bob - 博客园

https://wenku.youkuaiyun.com/answer/50567c95c5cb49059f471019c24b612b

ROS2 官方教程翻译(一) 关于组件(About Composition)_ros2 component-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值