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示例,它每秒发布一条消息到特定的主题。
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
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,并在构造函数中初始化所需的资源。
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_;
};
// 使用宏注册组件,使其可被动态加载
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
// 定义插件接口
class MyPluginInterface
{
public:
virtual ~MyPluginInterface() {} // 虚析构函数
virtual void printMessage(const std::string &message) = 0; // 纯虚函数
};
3.2 实现插件
接下来,实现该接口的插件,命名为 MyPlugin,并实现 printMessage 函数。
// my_plugin.cpp
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
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
class MyPluginInterface
{
public:
virtual ~MyPluginInterface() {} // 虚析构函数
virtual void printMessage(const std::string &message) = 0; // 纯虚函数
};
4.2 实现插件
实现 MyPlugin 插件。
// my_plugin.cpp
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
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
点击“阅读原文”查看详情