介绍
ROS 2 引入了受管节点的概念,也称为 LifecycleNode
。在下面的教程中,我们将解释这些节点的用途、它们与常规节点的不同之处以及它们如何遵守生命周期管理。受管节点包含具有一组预定义状态的状态机。这些状态可以通过调用指示后续连续状态的转换 ID 来更改。状态机的实现如 ROS 2 设计页面 https://design.ros2.org/articles/node_lifecycle.html 中所述。
我们的实现区分 Primary States
和 Transition States
。主要状态应该是稳定状态,其中任何节点都可以执行相关任务。另一方面,过渡状态是指附属于过渡的临时中间状态。这些中间状态的结果用于指示两个主要状态之间的转换是否被视为成功。因此,任何被管节点都可以处于以下状态之一:
主要状态(稳态):
unconfigured 未配置
inactive 不活跃
active 活跃状态
shutdown 关机
过渡状态(中间状态):
configuring 配置
activating 启动
deactivating 停用
cleaningup 清理中
shuttingdown 关机
可能要调用的转换是:
configure 配置
activate 启动
deactivate 禁用
cleanup 清理
shutdown 关机
为了更详细地解释所应用的状态机,我们参考了设计页面,该页面详细解释了每个状态和转换。
演示
发生了什么
演示被分为三个独立的应用程序:
lifecycle_talker 生命周期讲述者
lifecycle_listener 生命周期监听器
lifecycle_service_client 生命周期服务客户端
lifecycle_talker
表示一个受控节点,并根据节点的状态进行发布。我们将讲述节点的任务分成几个部分,并按如下方式执行:
配置:我们初始化了发布器和计时器
激活:我们激活发布者和计时器以便进行发布
我们停止发布者和计时器
清理:我们销毁了发布者和计时器
#include <chrono> // 包含用于处理时间的库
#include <memory> // 包含智能指针库
#include <string> // 包含字符串库
#include <thread> // 包含线程库
#include <utility> // 包含实用工具库
#include "lifecycle_msgs/msg/transition.hpp" // 包含生命周期消息中的转换消息
#include "rclcpp/rclcpp.hpp" // 包含ROS 2客户端库
#include "rclcpp/publisher.hpp" // 包含ROS 2的发布者库
#include "rclcpp_lifecycle/lifecycle_node.hpp" // 包含生命周期节点库
#include "rclcpp_lifecycle/lifecycle_publisher.hpp" // 包含生命周期发布者库
#include "std_msgs/msg/string.hpp" // 包含标准消息中的字符串消息
using namespace std::chrono_literals; // 使用chrono库中的字面量表示法
/// LifecycleTalker类继承自rclcpp_lifecycle::LifecycleNode
/**
* LifecycleTalker类并不像常规的“talker”节点那样继承自Node,而是继承自LifecycleNode。
* 这带来了一组回调函数,根据节点的当前状态被调用。
* 每个生命周期节点都有一组服务附加到它上面,这些服务使其可以从外部控制并触发状态变化。
* Beta1版本中可用的服务:
* - <node_name>__get_state
* - <node_name>__change_state
* - <node_name>__get_available_states
* - <node_name>__get_available_transitions
* 另外,还创建了一个用于状态变化通知的发布者:
* - <node_name>__transition_event
*/
class LifecycleTalker : public rclcpp_lifecycle::LifecycleNode
{
public:
/// LifecycleTalker构造函数
/**
* LifecycleTalker/LifecycleNode构造函数与常规节点的构造函数相同。
*/
explicit LifecycleTalker(const std::string & node_name, bool intra_process_comms = false)
: rclcpp_lifecycle::LifecycleNode(node_name,
rclcpp::NodeOptions().use_intra_process_comms(intra_process_comms)) // 初始化父类LifecycleNode
{}
/// 用于定时器回调以发布消息的函数
/**
* 用于定时器回调的函数。此函数由定时器调用并执行发布操作。
* 在此演示中,我们询问节点的当前状态。如果生命周期发布者未激活,我们仍然调用发布,但通信被阻止,因此实际上没有消息被传输。
*/
void
publish()
{
static size_t count = 0; // 静态计数器
auto msg = std::make_unique<std_msgs::msg::String>(); // 创建一个唯一指针的字符串消息
msg->data = "Lifecycle HelloWorld #" + std::to_string(++count); // 设置消息内容
// 打印当前状态以供演示
if (!pub_->is_activated()) {
RCLCPP_INFO(
get_logger(), "Lifecycle publisher is currently inactive. Messages are not published."); // 如果发布者未激活,打印信息
} else {
RCLCPP_INFO(
get_logger(), "Lifecycle publisher is active. Publishing: [%s]", msg->data.c_str()); // 如果发布者已激活,打印并发布信息
}
// 我们在任何状态下都调用生命周期发布者的发布函数。
// 只有在发布者处于激活状态时,消息传输才会启用并且消息实际发布。
pub_->publish(std::move(msg)); // 发布消息
}
/// 用于配置状态的过渡回调函数
/**
* on_configure回调函数在生命周期节点进入“configuring”状态时调用。
* 根据此函数的返回值,状态机要么触发到“inactive”状态的过渡,要么保持在“unconfigured”状态。
* TRANSITION_CALLBACK_SUCCESS 过渡到“inactive”
* TRANSITION_CALLBACK_FAILURE 过渡到“unconfigured”
* TRANSITION_CALLBACK_ERROR 或任何未捕获的异常则过渡到“errorprocessing”
*/
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn
on_configure(const rclcpp_lifecycle::State &)
{
// 此回调用于初始化和配置。
// 因此我们初始化和配置我们的发布者和定时器。
// 生命周期节点API返回生命周期组件,例如生命周期发布者。这些实体遵循生命周期并能遵守节点的当前状态。
// 在beta版本中,目前只有一个生命周期发布者可用。
pub_ = this->create_publisher<std_msgs::msg::String>("lifecycle_chatter", 10); // 创建生命周期发布者
timer_ = this->create_wall_timer(
1s, [this]() {return this->publish();}); // 创建定时器
RCLCPP_INFO(get_logger(), "on_configure() is called."); // 打印配置信息
// 我们返回成功并因此触发到下一个步骤:“inactive”的过渡。
// 如果我们返回TRANSITION_CALLBACK_FAILURE,状态机将保持在“unconfigured”状态。
// 如果在此回调中发生TRANSITION_CALLBACK_ERROR或任何抛出的异常,状态机将过渡到“errorprocessing”状态。
return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS; // 返回成功
}
/// 用于激活状态的过渡回调函数
/**
* on_activate回调函数在生命周期节点进入“activating”状态时调用。
* 根据此函数的返回值,状态机要么触发到“active”状态的过渡,要么保持在“inactive”状态。
* TRANSITION_CALLBACK_SUCCESS 过渡到“active”
* TRANSITION_CALLBACK_FAILURE 过渡到“inactive”
* TRANSITION_CALLBACK_ERROR 或任何未捕获的异常则过渡到“errorprocessing”
*/
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn
on_activate(const rclcpp_lifecycle::State & state)
{
// 父类方法自动在托管实体(目前是LifecyclePublisher)上过渡。
// 这里也可以手动调用pub_->on_activate()。
// 重写此方法是可选的,很多时候默认方法就足够了。
LifecycleNode::on_activate(state);
RCLCPP_INFO(get_logger(), "on_activate() is called."); // 打印激活信息
// 睡眠2秒。
// 我们模拟在激活阶段进行重要工作。
std::this_thread::sleep_for(2s); // 休眠2秒
// 我们返回成功并因此触发到下一个步骤:“active”的过渡。
// 如果我们返回TRANSITION_CALLBACK_FAILURE,状态机将保持在“inactive”状态。
// 如果在此回调中发生TRANSITION_CALLBACK_ERROR或任何抛出的异常,状态机将过渡到“errorprocessing”状态。
return rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS; // 返回成功
}
/// 用于停用状态的过渡回调函数
/**
* on_deactivate回调函数在生命周期节点进入“deactivating”状态时调用。
* 根据此函数的返回值,状态机要么触发到“inactive”状态的过渡,要么保持在“active”状态。
* TRANSITION_CALLBACK_SUCCESS 过渡到“inactive”
* TRANSITION_CALLBACK_FAILURE 过渡到“active”
* TRANSITION_CALLBACK_ERROR 或任何未捕获的异常则过渡到“errorprocessing”
*/
rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn
on_deactivate(const rclcpp_lifecycle::State & state)
{
// 父类方法自动在托管实体(目前是LifecyclePublisher)上过渡。
// 这里也可以手动调用pub_->on_deactivate()。
// 重写此方法是可选的,很多时候默认方法就足够了。
LifecycleNode::on_deactivate(state);
RCLCPP_INFO(get_logger(), "on_deactivate() is called."); // 打印停用信息
// 我们返回成功并因此触发到下一个步骤:“inactive”的过渡。
// 如果我们返回TRANSITION_CALLBACK_FAILURE,状态机将保持在“active”状态。
// 如果在此回调中发生TRANSITION_CALLBACK_ERROR或任何抛出的异常,状态机将过渡到“errorprocessing”状态。
return rclcpp_lifec