目录
ROS 1 - 节点 vs. 节点组
ROS 2 - 统一 API
编写组件
使用组件
实际应用
ROS 1 - 节点 vs. 节点组
在 ROS 1 中,您可以将代码编写为 ROS node 或 ROS nodelet 。ROS 1 节点被编译成可执行文件。另一方面,ROS 1 nodelet 被编译成共享库,然后由容器进程在运行时加载。
ROS 2 - 统一 API
在 ROS 2 中,推荐的编写代码方式类似于 nodelet - 我们称之为 Component
。这使得将常见概念添加到现有代码中变得容易,比如生命周期 https://design.ros2.org/articles/node_lifecycle.html 。在 ROS 1 中最大的缺点是有不同的 API,而在 ROS 2 中避免了这一点,因为两种方法使用相同的 API。
注意
仍然可以使用“编写自己的主程序”的节点样式,但对于常见情况,不推荐这样做。
通过将流程布局设为部署时的决策,用户可以选择:
在单独的进程中运行多个节点,具有进程/故障隔离的好处以及更容易调试单个节点
在单个进程中运行多个节点,具有较低的开销,并且可选择更高效的通信(参见进程内通信 https://docs.ros.org/en/jazzy/Tutorials/Demos/Intra-Process-Communication.html )。
此外, ros2 launch
可以通过专门的启动操作来自动化这些操作。
编写组件
由于组件仅内置于共享库中,因此它没有 main
函数(请参阅 Talker 源代码 https://github.com/ros2/demos/blob/jazzy/composition/src/talker_component.cpp )。组件通常是 rclcpp::Node
的子类。由于它不控制线程,因此不应在其构造函数中执行任何长时间运行或阻塞的任务。相反,它可以使用计时器来获取定期通知。此外,它可以创建发布者、订阅者、服务器和客户端。
#include "composition/talker_component.hpp" // 包含"composition/talker_component.hpp"头文件
#include <chrono> // 包含时间库
#include <iostream> // 包含输入输出流库
#include <memory> // 包含智能指针库
#include <utility> // 包含工具库
#include "rclcpp/rclcpp.hpp" // 包含rclcpp库
#include "std_msgs/msg/string.hpp" // 包含标准消息库
using namespace std::chrono_literals; // 使用chrono字面量命名空间
namespace composition // 定义composition命名空间
{
// 创建一个Talker "组件",它继承了通用的rclcpp::Node基类。
// 组件被构建成共享库,因此不编写自己的main函数。
// 使用组件的共享库的进程将实例化该类作为ROS节点。
Talker::Talker(const rclcpp::NodeOptions & options)
: Node("talker", options), count_(0) // 构造函数初始化列表,设置节点名称为"talker",并初始化count_为0
{
// 创建一个在"chatter"主题上发布"std_msgs/String"消息的发布者。
pub_ = create_publisher<std_msgs::msg::String>("chatter", 10); // 创建发布者,队列大小为10
// 使用定时器安排周期性消息发布。
timer_ = create_wall_timer(1s, [this]() {return this->on_timer();}); // 创建定时器,每1秒调用一次on_timer函数
}
void Talker::on_timer() // 定时器回调函数
{
auto msg = std::make_unique<std_msgs::msg::String>(); // 创建一个唯一指针,指向std_msgs::msg::String类型的消息
msg->data = "Hello World: " + std::to_string(++count_); // 设置消息数据为"Hello World: "加上当前计数
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", msg->data.c_str()); // 打印日志信息
std::flush(std::cout); // 刷新标准输出流
// 将消息放入队列中由中间件处理。
// 这个调用是非阻塞的。
pub_->publish(std::move(msg)); // 发布消息
}
} // namespace composition
#include "rclcpp_components/register_node_macro.hpp" // 包含rclcpp组件注册宏头文件
// 使用class_loader注册组件。
// 这作为一种入口点,使组件在其库加载到运行进程中时可被发现。
RCLCPP_COMPONENTS_REGISTER_NODE(composition::Talker) // 注册composition::Talker组件
将这样的类制作成组件的一个重要方面是,该类使用包 rclcpp_components
中的宏注册自身(请参见源代码的最后一行)。这使得在将其库加载到正在运行的进程中时可以发现该组件 - 它充当了一种入口点。
此外,一旦组件创建完成,必须将其注册到索引中,以便工具能够发现它。
add_library(talker_component SHARED src/talker_component.cpp)
rclcpp_components_register_nodes(talker_component "composition::Talker")
# To register multiple components in the same shared library, use multiple calls
# rclcpp_components_register_nodes(talker_component "composition::Talker2")
例如,请查看本教程
https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Writing-a-Composable-Node.html
注意
为了使 component_container 能够找到所需的组件,必须从已获取相应工作区的 shell 中执行或启动它。
使用组件
组合包https://github.com/ros2/demos/tree/jazzy/composition 包含几种不同的方法来使用组件。最常见的三种方法是:
启动一个(通用容器进程 https://github.com/ros2/rclcpp/blob/jazzy/rclcpp_components/src/component_container.cpp )并调用容器提供的 ROS 服务 load_node。然后,ROS 服务将加载由传递的包名和库名指定的组件,并在正在运行的进程中开始执行它。除了以编程方式调用 ROS 服务外,您还可以使用命令行工具通过传递的命令行参数来调用 ROS 服务。
创建一个包含多个在编译时已知节点的自定义可执行 https://github.com/ros2/demos/blob/jazzy/composition/src/manual_composition.cpp 文件。这种方法要求每个组件都有一个头文件(对于第一种情况,这不是严格需要的)。
创建一个启动文件,并使用
ros2 launch
创建一个加载了多个组件的容器进程。
实际应用
试用 Composition 演示。https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Composition.html