返回专辑
·Johan·3 分钟阅读

Lifecycle Node:把资源生命周期显式化,而不是构造即抢设备

configure/activate/cleanup 对称;关键路径用生命周期,工具节点保持简单。

Lifecycle Node:把资源生命周期显式化,而不是构造即抢设备

1. 节点在、设备没有

底盘驱动节点进程活着,ros2 node list 能看到名字,但 /cmd_vel 无响应——构造函数里打开了串口,中途异常后句柄泄漏,对外却像健康。普通节点一构造就抢资源;失败与半初始化难区分。Lifecycle 把 unconfigured → inactive → active → finalized 拆开,让运维可以安全重配,让 bringup 可以按依赖排序。半初始化比直接崩溃更危险——上层会信假数据。

2. 状态与回调职责

cpp
class MyDriver : public rclcpp_lifecycle::LifecycleNode {
  CallbackReturn on_configure(const State &) override {
    // 分配、读参、打开设备;不开始对外刷数据
    port_ = open_serial(param_port_);
    return port_ ? CallbackReturn::SUCCESS : CallbackReturn::FAILURE;
  }
  CallbackReturn on_activate(const State &) override {
    // 开始发布与控制
    timer_ = create_wall_timer(10ms, [this]{ publish(); });
    return CallbackReturn::SUCCESS;
  }
  CallbackReturn on_deactivate(const State &) override {
    timer_.reset();
    send_zero_torque();  // 安全停,不是只停发布
    return CallbackReturn::SUCCESS;
  }
  CallbackReturn on_cleanup(const State &) override {
    close(port_); port_ = -1;
    return CallbackReturn::SUCCESS;
  }
};

on_configure 失败应停留 unconfigured,不得半开设备。on_activate 失败须留可诊断日志。on_deactivate 必须保证命令口安全——力矩零/刹车——而不是只停 timer。很多驱动把「断使能」写在析构里;lifecycle 热切换不走析构,安全停必须显式写在 deactivate/cleanup。

3. 对称释放与第二次启动

违反对称是泄漏与「第二次启动失败」之源:第一次 activate 打开的句柄,cleanup 没关,下次 configure 踩自己。测试必须覆盖:

  • configure 失败路径(设备不存在)
  • activate 后 deactivate → 再次 configure/activate 不泄漏
  • 拔掉设备,节点不得假 active
bash
ros2 lifecycle set /my_driver configure
ros2 lifecycle set /my_driver activate
ros2 lifecycle set /my_driver deactivate
ros2 lifecycle set /my_driver cleanup
ros2 lifecycle set /my_driver configure  # 第二次应成功

4. 谁值得 lifecycle

值得不值得
独占硬件的驱动无状态格式转换
发布「看起来合法」传感/控制的节点一次性脚本
需要按依赖排序启动的核心服务纯订阅/log 节点

ros2_control 的控制器加载/激活与硬件组件 lifecycle 是同一思想——资源与策略的显式状态机。Lifecycle 单独存在不够,还需要 bringup 编排:下游在上游 active 后再激活。

5. 与 bringup、诊断的交点

节点状态应反映到 /diagnosticslifecycle_state 字段让运维一眼看到 inactive 而非猜日志。ros2 lifecycle get /node 是 CLI 验收入口。不会 lifecycle 的团队常靠 kill + relaunch 续命——短时间有效,却让资源泄漏永远不见光。Lifecycle 逼你把清理写出来;写不出来的清理,会在热切换与 CI 反复启动里爆出来。

6. 迁移策略

半迁(只有 configure,没有对称 cleanup)比不迁更糟——状态名加了,泄漏还在。迁移顺序:

  1. 先保证 deactivate 安全停(力矩零/刹车)
  2. 再写对称 cleanup(关句柄、释资源)
  3. 最后接 lifecycle_manager 编排

迁 lifecycle 的第一周往往更「脆」:以前藏着的问题变成显式失败。用诊断证明失败更可定位,顶住回退压力。对称清理稳定后,现场重启次数应下降——若没有,说明只加了状态名,没加安全停与清理。

7. 验收

  • configure 失败:停留 unconfigured,日志写明原因。
  • deactivate:命令口安全(零力矩/刹车),不是只停发布。
  • 第二次 configure/activate:无泄漏、无「address already in use」。
  • 诊断反映 lifecycle 状态;拔设备不得假 active。

7. 参数在 lifecycle 各阶段的读取

on_configure 读静态参数(设备路径、分辨率);on_activate 可读动态参数(发布频率)。在 inactive 期间改参数不应触发发布——参数回调里检查 get_current_state().id() 是否为 active。与参数声明文一致:仅 configure 可读的参数,不要在 active 偷偷生效一半。

8. on_shutdown 与 finalized

on_shutdown 在进程退出前调用,做最后清理。与 on_cleanup 区别:cleanup 后可再次 configure(热重启);shutdown 是终态。若只在 shutdown 断使能、不在 deactivate 断,热切换时命令口仍可能输出非零力矩——安全审计会卡在这里。

rclcpp_lifecyclecreate_publisher 在 inactive 时不应发数据;用 LifecyclePublisher 或在 activate 里才 create timer。普通 publisher 在 configure 就开,inactive 期间若误触 timer,会发布 stale 数据。

9. 与录包复盘

事故袋应含节点 lifecycle transition 或 diagnostics,才能区分「驱动 inactive」与「控制算法失败」。缺状态层,复盘只能猜「为什么不动」——证据链不完整。

10. 最小状态机测试

联调脚本对关键驱动跑一轮:configure → activate → deactivate → cleanup → 再 configure。第二次 activate 必须成功;命令口在 deactivate 后采样应为安全值。跳过这轮「对称性」测试,泄漏往往拖到现场换班才爆。

← 全部文章

johan's blog