0. 简介
ros1中我们会经常使用Nodelets来完成多个Node节点的管理。但是在ROS2中如何使用多节点组合去完成管理,这也是我们需要去了解和学习的。
1 . ROS 1 - Node 和 Nodelets
在ROS1中你可以写一个节点也可以写一个小节点(Nodelet)。 ROS 1 的节点会被编译成一个可执行文件。 ROS 1的小节点会被编译成一个动态链接库。当程序运行的时候会被动态的加载到容器进程里面。Nodelet包就是为改善这一状况设计的,它提供一种方法,可以让多个算法程序在一个进程中用 shared_ptr 实现零拷贝通信(zero copy transport),以降低因为传输大数据而损耗的时间。简单讲就是可以将多个node捆绑在一起管理,使得同一个manager里面的topic的数据传输更快。
1.1 pkg组织框架:
- nodelet头文件(include/)
- nodelet源文件(src/)
- nodelet_plugins.xml
- package.xml
- CMakeLists.txt
- launch文件
1.2 需要的操作步骤:
- 在.cpp和.h中添加相应的头文件;
- 去掉main函数;
- .h中声明子类,.cpp中定义子类;
- .cpp中将原node中类构造函数代码移动到onInit()中,用于初始化;
- .cpp中添加PLUGINLIB_EXPORT_CLASS宏,讲子类声明为插件类,并编译为动态库;
- package中添加
依赖,添加export; - 编写plugin.xml使nodelet称为一个插件;
- CMakeLists做相应修改:添加nodelet依赖;没有main因此无executable,而是lib。
1.3 NodeLets子类
MyNodeletClass.h
#include <nodelet/nodelet.h>
namespace example_pkg
{
class MyNodeletClass : public nodelet::Nodelet
{
public:
virtual void onInit();
};
}
注意:在nodelet::Nodelet基类当中,onInit()被声明为纯虚函数,因此在子类中必须对其进行重写(override)。所有的初始化工作都应该在这个函数中完成。
MyNodeletClass.cpp
#include <pluginlib/class_list_macros.h>
// watch the capitalization carefully
PLUGINLIB_EXPORT_CLASS(example_pkg::MyNodeletClass, nodelet::Nodelet)
namespace example_pkg
{
void MyNodeletClass::onInit()
{
NODELET_DEBUG("Initializing nodelet...");
}
}
注意:为了允许类被动态加载,它必须被标记为导出类。这通过特殊宏PLUGINLIB_EXPORT_CLASS / PLUGINLIB_DECLARE_CLASS来完成,通常放在导出类的.cpp文件的末尾。宏的参数分类为:pkg,class_name,class_type ,base_class_type。
1.4 nodelet_plugins.xml
该文件应与package.xml放置在同一路径下。插件描述文件是一个XML文件,用于存储有关插件的所有重要信息。它包含有关插件所在的库的信息,插件的名称,插件的类型等。
<library path="lib/libMyNodeletClass">
<class name="example_pkg/MyNodeletClass" type="example_pkg::MyNodeletClass" base_class_type="nodelet::Nodelet">
<description>
This is my nodelet.
</description>
</class>
</library>
1.5 package.xml
package.xml文件中,为了让pluginlib查询ROS系统上的所有可用插件,每个包必须显式指定它导出的插件。应添加如下:
...
<build_depend>nodelet</build_depend>
<run_depend>nodelet</run_depend>
<export>
<nodelet plugin="${prefix}/nodelet_plugins.xml" />
</export>
...
1.6 mynodelet.launch
launch file中做了两件事情:启动一个nodelet manager,加载nodelet到manager中。
<launch>
<node pkg="nodelet" type="nodelet" name="standalone_nodelet" args="manager" output="screen"/>
<node pkg="nodelet" type="nodelet" name="MyNodeletClass"
args="load example_pkg/MyNodeletClass standalone_nodelet" output="screen">
</node>
</launch>
2. ROS 2 - 统一API
在ROS2里面,推荐编写小节点——我们称之为组件Component
。 这样我们就更容易为已经存在的代码添加一些通用的概念,比如生命周期. 使用不同的API所带来的麻烦完全被ROS2给避免了。节点进和小节点在ROS2中完全使用相同的API。
你也可以继续使用节点的风格的主函数,但是一般是并不推荐的
通过把进程的结构变成一个部署是的选项,用户可以自由的在下面的模式进行选择
- 在不同的进程中运行多个节点。这样可以使不同的进程独立开。一个崩溃其他可以正常运行。也更方便调试各个节点。
- 在同一个进程中运行多个节点。这样可以使得通信更加高效。
在未来的roslaunch版本中,会支持配置进程的结构,官网说明。
这些demos使用rclcpp_components, ros2component
和composition
包里面可执行文件,并且可以使用以下指令运行。
第一个终端,运行组件容器:
ros2 run rclcpp_components component_container
使用ros2
命令行确认正在运行中的容器
$ ros2 component list
/ComponentManager
在第二个终端(查看talker源码(https://github.com/ros2/demos/blob/foxy/composition/src/talker_component.cpp)).这个指令会返回已启动的模块id,其跟节点名字一样独一无二。
$ ros2 component load /ComponentManager composition composition::Talker
Loaded component 1 into '/ComponentManager' container node as '/talker'
第二个终端的另一条指令(查看listener代码(https://github.com/ros2/demos/blob/foxy/composition/src/listener_component.cpp)):
$ ros2 component load /ComponentManager composition composition::Listener
Loaded component 2 into '/ComponentManager' container node as '/listener'
ros2命令行实用程序现在是可以用来检查容器的状态:
$ ros2 component list
/ComponentManager
1 /talker
2 /listener
使用launch操作组合,命令行工具用来调试和诊断组件配置,它通常是在同一时间启动一组组件(的方式)更方便。为了使得该操作自动化(完成),我们可以使用ros2 launch功能指令。
ros2 launch composition composition_demo.launch.py
最后可以使用专属的ID来卸载组件容器的节点。
$ ros2 component unload /ComponentManager 1 2
Unloaded component 1 from '/ComponentManager' container
Unloaded component 2 from '/ComponentManager' container
下面我们来看一下函数结构
2.1 CMakeList.txt
为了使component_container能够找到所需的组件,必须在索引中注册该组件rclcpp_components_register_nodes
,以便工具可以发现它:
add_library(talker_component SHARED
src/talker_component.cpp)
target_compile_definitions(talker_component
PRIVATE "COMPOSITION_BUILDING_DLL")
ament_target_dependencies(talker_component
"rclcpp"
"rclcpp_components"
"std_msgs")
rclcpp_components_register_nodes(talker_component "composition::Talker")
set(node_plugins "${node_plugins}composition::Talker;$<TARGET_FILE:talker_component>\n")
add_executable(manual_composition
src/manual_composition.cpp)
target_link_libraries(manual_composition
talker_component
listener_component
server_component
client_component)
ament_target_dependencies(manual_composition
"rclcpp")
add_executable(linktime_composition
src/linktime_composition.cpp)
set(libs
talker_component
listener_component
server_component
client_component)
2.2 package.xml
package.xml文件中,需要添加对应的rclcpp_components
的components依赖:
<build_depend>example_interfaces</build_depend>
<build_depend>rclcpp</build_depend>
<build_depend>rclcpp_components</build_depend>
<build_depend>rcutils</build_depend>
<build_depend>std_msgs</build_depend>
<exec_depend>example_interfaces</exec_depend>
<exec_depend>launch_ros</exec_depend>
<exec_depend>rclcpp</exec_depend>
<exec_depend>rclcpp_components</exec_depend>
<exec_depend>rcutils</exec_depend>
<exec_depend>std_msgs</exec_depend>
<test_depend>ament_cmake_pytest</test_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<test_depend>launch</test_depend>
<test_depend>launch_testing</test_depend>
<test_depend>launch_testing_ros</test_depend>
<test_depend>launch_testing_ament_cmake</test_depend>
<test_depend>rmw_implementation_cmake</test_depend>
2.3 talker 运行
talker_component.hpp主程序当中头文件主要是用来申明组件
#ifndef COMPOSITION__TALKER_COMPONENT_HPP_
#define COMPOSITION__TALKER_COMPONENT_HPP_
#include "composition/visibility_control.h"
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
namespace composition
{
class Talker : public rclcpp::Node
{
public:
COMPOSITION_PUBLIC
explicit Talker(const rclcpp::NodeOptions & options);
protected:
void on_timer();
private:
size_t count_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
rclcpp::TimerBase::SharedPtr timer_;
};
} // namespace composition
#endif // COMPOSITION__TALKER_COMPONENT_HPP_
talker_component.cpp主函数内部和ROS1一样只存在函数调用,并通过RCLCPP_COMPONENTS_REGISTER_NODE
完成了组件的生成
#include "composition/talker_component.hpp"
#include <chrono>
#include <iostream>
#include <memory>
#include <utility>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
namespace composition
{
// Create a Talker "component" that subclasses the generic rclcpp::Node base class.
// Components get built into shared libraries and as such do not write their own main functions.
// The process using the component's shared library will instantiate the class as a ROS node.
Talker::Talker(const rclcpp::NodeOptions & options)
: Node("talker", options), count_(0)
{
// Create a publisher of "std_mgs/String" messages on the "chatter" topic.
pub_ = create_publisher<std_msgs::msg::String>("chatter", 10);
// Use a timer to schedule periodic message publishing.
timer_ = create_wall_timer(1s, std::bind(&Talker::on_timer, this));
}
void Talker::on_timer()
{
auto msg = std::make_unique<std_msgs::msg::String>();
msg->data = "Hello World: " + std::to_string(++count_);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", msg->data.c_str());
std::flush(std::cout);
// Put the message into a queue to be processed by the middleware.
// This call is non-blocking.
pub_->publish(std::move(msg));
}
} // namespace composition
#include "rclcpp_components/register_node_macro.hpp"
// Register the component with class_loader.
// This acts as a sort of entry point, allowing the component to be discoverable when its library
// is being loaded into a running process.
RCLCPP_COMPONENTS_REGISTER_NODE(composition::Talker)
launch 文件
composition_demo.launch.py使用launch操作组合,命令行工具用来调试和诊断组件配置,它通常是在同一时间启动一组组件(的方式)更方便。
import launch
from launch_ros.actions import ComposableNodeContainer
from launch_ros.descriptions import ComposableNode
def generate_launch_description():
"""Generate launch description with multiple components."""
container = ComposableNodeContainer(
name='my_container',
namespace='',
package='rclcpp_components',
executable='component_container',
composable_node_descriptions=[
ComposableNode(
package='composition',
plugin='composition::Talker',
name='talker'),
ComposableNode(
package='composition',
plugin='composition::Listener',
name='listener')
],
output='screen',
)
return launch.LaunchDescription([container])
3. 参考链接
https://zhuanlan.zhihu.com/p/355187545
https://zhuanlan.zhihu.com/p/37537823
https://blog.csdn.net/qq_45701501/article/details/119281785
评论(0)
您还未登录,请登录后发表或查看评论