清晨九点,一家餐厅准备开门营业。如果店长只是喊一嗓子“开工了”,厨房多半会乱成一团:切配师傅不知道先切什么、炒锅师傅的油还没烧热、摆盘台前空空如也。有经验的店长会拿一份“开店清单”——几点开几个工作站、每个灶台设什么火候、先备料再开火、最后才迎客。这份清单,就是 ROS2 中 launch 文件的职责所在;而挂在每个工作站门口的状态牌——“关闭”“待命”“开工”“收工”——对应的正是 ROS2 生命周期节点的四种状态。本文就用“餐厅开店”这条主线,带你一次看懂 launch 文件如何批量启动和配置机器人节点,以及生命周期状态机如何让节点受控地进入工作状态。
学会 launch 与生命周期,你就能像店长指挥餐厅一样从容启动整个机器人系统——这是从单节点 demo 迈向多节点真实工程的分水岭,也是 ROS2 面试中出现频率最高的考点之一。
可视化演示
分步展示launch文件节点启动流程、生命周期节点状态机转换与组合节点进程内通信。
本节配有交互动效(进入视口后自动加载,约 3KB)。
步骤1:Launch文件节点启动 Python/XML launch文件定义节点启动顺序 参数加载与话题重映射配置 Launch = nodes + params + remap launch文件统一管理ROS2节点启动与配置。
launch文件概述
店长的开店清单,听起来很简单,但真做起来并不容易:哪几个工作站今天开?每个站需要什么食材和火候?谁先开工、谁后开工?这份清单本质上是一整套“启动编排”。ROS2 里的 launch 文件,就是机器人世界那张开店清单——它把“启动哪些程序、传什么参数、按什么顺序”全部写在文件里,交给 ros2 launch 一次性执行。
在 ROS2 中,每个“干活的程序”叫节点(Node),就像厨房里一个个工作站。早期的做法是开好几个终端,分别敲 ros2 run 包名 节点名 手动启动。可一个真实的机器人系统动辄十几二十个节点:激光雷达驱动、相机驱动、定位、建图、路径规划、底盘控制……靠人肉敲命令既慢又容易漏参数,就像店长挨个喊话传菜,迟早出错。
launch 文件正是为了解决这个痛点而生,它主要做三件事:批量启动节点、统一下发参数、按依赖关系编排顺序。它还支持“嵌套”——一份大的 launch 可以 include 多份子 launch,相当于把整个餐厅拆成冷菜间、热菜间、甜品间各自的开店清单,再由总店长汇总。编写格式主要有 Python 与 XML 两种,下一节逐一展开。
用集合的眼光看,一份 launch 描述就是一组节点配置的集合:
说到底,开店清单的价值不在于“记了哪些菜”,而在于“让整个厨房按秩序运转”。launch 文件同样如此——它是机器人系统的第一道工序,把混乱的启动过程变成可复现、可维护的工程资产。
- LaunchDescription 启动描述整个“开店清单”的总称,一次启动任务的所有描述都装在这个对象里。
- Node 节点动作清单中的第 i 个工作站(节点),声明“从哪个包、启动哪个程序”。
- 集合大括号表示清单是“一组动作的集合”,集合中的顺序就是启动先后顺序。
Python编写launch
写清单有两种风格:一种是“可编程清单”——用 Python 脚本动态生成开店安排;另一种是“表格单据”——用 XML 标签把配置填进固定格子。ROS2 官方更推荐前者,因为 Python 可以写循环、条件判断、变量替换,灵活得像一个自带计算器的店长。
一个 Python launch 文件的核心是一个入口函数 generate_launch_description()。ROS2 的启动器读到这个文件后,会调用这个函数,拿到它返回的 LaunchDescription 对象——整个开店清单的“最终版”。函数内部,我们通过 Node(...) 来声明每个要启动的节点:指定 package(哪个功能包)、executable(哪个可执行程序)、name(给节点起什么名字),还可以通过 parameters 传入参数文件或参数字典。
除了启动节点,Python launch 还能做重映射(remapping):把节点内部的话题名换成外部约定的名字,好比同一道菜在堂食菜单和外卖菜单上叫不同名字。此外,IncludeLaunchDescription 可以把其他 launch 文件整体“嵌套”进来,还能用 PythonLaunchDescriptionSource 指定子清单的路径,实现子系统复用。这让 launch 具备了“组装”能力:
用 Python 写 launch,就像店长用一份“可编程的活页夹”管理后厨:备料多少可以由人流量动态算出来,开几个灶台可以由时间段自动决定。对于复杂的机器人系统,这种动态表达能力是 XML 难以替代的。
- 入口函数 generate_launch_descriptionROS2 启动器约定俗成地调用这个函数来“取清单”,它是 Python launch 的固定入口。
- Node 节点声明用一个 Node 动作描述“启动哪个工作站的哪个程序”,参数依次是包名、可执行名、节点名。
- params 参数配置通过 parameters 字段给节点传入参数文件或键值对,相当于给工作站下发食材和火候设置。
XML编写launch
如果说 Python 是可编程清单,那么 XML launch 就像一张“预制表格”——每个工作站该干什么、参数是多少,都填死在固定格子里。ROS2 保留了 XML 格式,主要是为了兼容从 ROS1 迁移过来的老系统,也因为它写起来非常紧凑直观。
一张 XML 清单以 <launch> 为根标签,像表格的外框。根标签内部,用 <node> 标签声明节点:pkg 属性写功能包名,exec 属性写可执行程序名,name 属性给节点命名。如果还要配参数,就在 <node> 内部嵌套 <param> 标签,用 name 和 value 指定参数名与值。整个结构层层嵌套,一眼就能看明白层级关系。
XML 的优点是“所见即所得”:标签结构清晰,适合简单、固定的启动场景。缺点也很明显——没有循环、没有条件判断,想“根据参数决定是否启动某个节点”就得借助外部工具或直接改用 Python。在真实项目中,这两者常被混合使用:简单系统用 XML 快速上手,复杂系统用 Python 保持灵活。它们的关系,就像表格单据与活页夹的关系:
两种格式没有绝对优劣:XML 像贴在墙上的固定排班表,适合一成不变的小店;Python 像能随时改动的数字化排班系统,适合需要灵活调度的大餐厅。初学者建议先把 XML 读熟,再主攻 Python,两条路都能走到同一个后厨。
<launch> 为根,内部用 <node> 声明节点、用 <param> 配置参数,结构即层级。- launch 根标签XML 格式的清单必须包裹在
<launch>根标签中,它相当于“这张表格的总标题”。 - node 标签用属性写明包名和程序名来声明一个节点,属性的写法就是表格里的“列名=取值”。
- param 标签嵌套在 node 内,用 name 和 value 指定参数,表示“这个工作站用什么样的火候”。
生命周期概念
开店清单解决了“谁先开工”的问题,却还没解决“工作站到底处于什么状态”。厨房里,每个工作站都应该挂一块状态牌:还没开张(关闭)、备料完毕但没开火(待命)、正在热火朝天炒菜(开工)、已经收工清洁(打烊)。ROS2 生命周期节点,就是给节点装上这样一块标准化的“状态牌”。
为什么需要状态管理?想一想:相机驱动节点如果参数还没加载完就往外发数据,下游的定位节点就会收到一堆“废帧”;底盘控制节点如果还没校准就开始给电机指令,后果更是不堪设想。普通节点启动即运行,无法保证“配置先于数据”;生命周期节点则把运行过程拆成明确的阶段,让系统的启动、停止、重配置都处于受控状态。
生命周期节点有四个主状态:unconfigured(未配置,相当于工作站刚开门,什么都还没准备)、inactive(未激活,配置好了但不出活)、active(已激活,正式对外工作)、finalized(已终结,清理完毕等待销毁)。状态之间靠“转换事件”驱动:configure、activate、deactivate、cleanup、shutdown。比如要从“未激活”变成“已激活”,就必须触发 activate 事件。这套模型用集合表示就是:
状态牌的意义,在于把“看不见的运行状态”变成“系统内外都能读懂的公共语言”。任何工具、任何开发者,只要看到节点的状态,就知道它此刻在不在干活、能不能安全地重启——这正是受控管理的价值所在。
- 状态变量 S系统为每个生命周期节点维护的“当前状态”,它随时间变化。
- Unconfigured 未配置节点刚启动或资源已释放时的状态,对应“工作站刚开门、锅碗还没摆好”。
- Inactive 未激活配置已完成但还没开始干活,对应“备料完毕但灶台没开火”。
- Active 已激活节点正式对外发布数据、执行任务,对应“灶台全开、正式出菜”。
- Finalized 已终结资源清理完毕、等待销毁,对应“工作站已清洁、准备锁门”。
生命周期回调
状态牌翻面的一瞬间,背后其实有一连串动作在发生。就像厨师把牌子从“待命”翻到“开工”之前,必须先点火、倒油、下料——这些准备工作就是生命周期回调函数要干的事。ROS2 为每一个转换事件都预留了一个对应的回调函数:on_configure、on_activate、on_deactivate、on_cleanup、on_shutdown。
开发者在节点类里重写这些回调,把“该做的事”填进去。例如 on_configure() 中加载参数、初始化传感器硬件;on_activate() 中创建发布者、开始发布数据;on_cleanup() 中释放内存、关闭设备。当外部发起一次转换请求时,框架会先调用对应的回调,并根据回调的返回值决定转换是否生效:返回成功,状态牌翻面;返回失败,状态牌原地不动,节点停留在原状态。
这种“先执行、后生效”的机制非常像餐厅的安全流程:灶台没点着火,就不能把菜下锅。回调失败不会让系统崩溃,也不会让节点跳到不可预期的状态,而是“拒绝迁移”并给出错误反馈,方便开发者排查。一次状态的迁移可以建模为:
回调机制把“状态切换”这件大事,拆解成一个一个可以重写、可以调试的小动作。就像店长不用关心炒菜细节,只需确认“每位厨师都在自己的岗位上完成了规定动作”——框架管流程,开发者管动作,各司其职。
- 迁移箭头 S_i 到 S_j表示一次状态转换,从旧状态指向新状态。
- callback 回调函数迁移瞬间执行的准备动作,返回“成功/失败”作为迁移是否合法的裁决。
- 回调集合每一种转换事件都有自己对应的 on_ 回调,例如激活回调专门负责“正式开工前的点火准备”。
组合节点
开一家大餐厅,有三种方案:租三间独立小厨房,各配一套水池、冰箱、灶台;或者把三个工作站放进同一间大厨房,共享水池和冰箱。后者显然更省空间、更省开销。ROS2 的组合节点(Composable Nodes)就是后一种思路:把多个节点放进同一个进程里运行。
普通方式下,每个节点都是一个独立进程,进程之间通过 DDS 跨进程通信,像三间小厨房之间要靠传菜窗口递菜。组合节点则把所有节点加载进一个“进程容器”(component container),节点之间直接走进程内通信(intra-process communication),省去了序列化、网络传输的开销,数据就像在大厨房里直接递盘子,又快又省。
在 launch 文件中使用组合节点,通常需要两步:先启动一个 ComposableNodeContainer(容器节点),再通过 LoadComposableNode 动作把各个组件加载进去。内存上,进程内共享资源和代码段,整体占用更低;调度上,少了一堆进程间的上下文切换,实时性更好。把多个节点放进同一进程,用数学语言描述就是:
组合节点不是银弹:如果某些节点需要在不同机器上分布式部署,或需要独立崩溃隔离,还是应该各开各的进程。它最适合“高频率、强耦合”的节点组团——就像把天天配合的三位师傅放到同一个厨房,效率立刻翻倍。
- 进程容器 P_container组合节点的“大厨房”,一个进程里同时承载多个节点。
- 并集符号表示 n 个节点被放进同一个进程,而不是各自单独开进程。
- 进程内通信节点之间通过共享内存等方式直接交换数据,标注为 intra-process,省去网络开销。
最佳实践
开店清单写得再漂亮,没有一套标准作业流程(SOP),餐厅还是容易出乱子。ROS2 工程同样需要规范:launch 文件怎么组织、参数怎么命名、生命周期用在哪儿、出问题怎么查。这些“工程素养”往往决定一个系统能跑 demo,还是能上产线。
在组织上,建议按功能拆分 launch 文件:一个 bringup.launch.py 负责总装,内部通过 include 引入 sensors.launch.py、navigation.launch.py、control.launch.py 等子清单,像总店长只协调几个大部门,而不是直接指挥每位厨师。参数命名要统一、有含义,避免 param1、value2 这种“天书”;尽量把参数外置到 YAML 文件,让配置和代码分离。
生命周期节点的使用时机也很有讲究:硬件驱动、传感器采集这类“必须先就绪再工作”的节点,是最典型的生命周期候选;而纯计算型节点如果只需要“启动就干活”,用普通节点反而更简单。调试方面,ros2 launch --print-description 可以打印最终清单看配置是否生效;ros2 lifecycle get /node_name 查看节点当前状态,ros2 lifecycle set /node_name configure 手动触发转换。启动顺序与依赖可以概括为:
优秀的工程实践,不是会写多少炫技代码,而是让系统“每次启动都稳定、每次出问题都能快速定位”。一张清晰的开店清单、一套受控的状态管理、一份规范的 SOP——这三样凑齐,你的机器人系统才算真正“能开门营业”。
- 启动顺序 Order_launchlaunch 文件编排的整体先后次序,像餐厅“先备料、再炒菜、后上菜”。
- drivers 驱动层最先启动硬件驱动节点,保证传感器数据有稳定来源。
- 箭头链表示层与层之间的数据依赖关系,前一层就绪后一层才启动。
AI 应用场景
移动机器人多传感器融合启动
阶段:系统部署与集成调试
解决的问题:一台仓储 AGV 上同时运行激光雷达、相机、IMU、融合定位、路径规划、底盘控制等 6 个以上节点。传统做法是手动开多个终端逐个启动,参数靠人肉记忆,顺序一错,融合节点就会订阅到空话题。用 launch 文件按“备料→炒菜→上菜”的思想编排:先启动传感器驱动,再启动融合定位,最后启动导航与底盘,所有参数统一收进 YAML 文件。
具体案例:某机器人创业公司在部署巡检机器人时,将传感器接入、SLAM 定位、导航避障三组节点拆成三个子 launch,由总 launch 嵌套组装。现场工程师只需一条 ros2 launch robot bringup.launch.py,机器人三分钟内完成整机启动,不再依赖个人记忆。
机械臂产线组合节点部署
阶段:性能优化与实时控制
解决的问题:机械臂的视觉识别、运动规划、伺服控制三个节点需要高频交换数据。跨进程通信一次的延迟虽小,但在控制周期 1ms 的产线上会被放大成明显的抖动。利用组合节点把三者放进同一个进程容器,通信降级为进程内共享内存,延迟与内存占用双降。
具体案例:一家 3C 电子装配厂的上料机械臂,原方案三个节点三个进程,实测控制周期抖动约 3ms;改用 ComposableNodeContainer 组合部署后,抖动降到 1ms 以内,抓取成功率显著提升。
自动驾驶实验室参数化复现实验
阶段:实验管理与结果复现
解决的问题:算法研究需要反复切换传感器配置、地图参数、算法开关,手动改代码既慢又容易污染实验版本。利用 launch 文件的嵌套与参数外置能力,把实验配置沉淀为“可复现的实验单据”。
具体案例:某自动驾驶实验室为每台实验车维护一套 launch 参数文件,实验前只需修改 YAML 中的车型参数,再启动统一的 launch 入口即可完成整套 sensors、perception、planning 节点的受控启动,实验可复现性大幅提升。
Python 演示
#!/usr/bin/env python3# 模拟 ROS2 生命周期状态机的极简演示, 不依赖 ROS2 环境也可运行 class LifecycleNode: def __init__(self, name): self.name = name self.state = 'unconfigured' def on_configure(self): print('[%s] on_configure: 加载参数与初始化硬件' % self.name) self.state = 'inactive' return 'success' def on_activate(self): print('[%s] on_activate: 开始发布数据' % self.name) self.state = 'active' return 'success' def on_deactivate(self): print('[%s] on_deactivate: 暂停任务' % self.name) self.state = 'inactive' return 'success' def on_cleanup(self): print('[%s] on_cleanup: 释放资源' % self.name) self.state = 'unconfigured' return 'success' def on_shutdown(self): print('[%s] on_shutdown: 清理并退出' % self.name) self.state = 'finalized' return 'success' def transition(self, event): if self.state == 'unconfigured' and event == 'configure': return self.on_configure() if self.state == 'inactive' and event == 'activate': return self.on_activate() if self.state == 'inactive' and event == 'cleanup': return self.on_cleanup() if self.state == 'active' and event == 'deactivate': return self.on_deactivate() if self.state == 'active' and event == 'cleanup': return self.on_cleanup() if event == 'shutdown': return self.on_shutdown() print('[%s] 非法转换: %s -> %s' % (self.name, self.state, event)) return 'error' def main(): node = LifecycleNode('camera_driver') events = ['configure', 'activate', 'deactivate', 'activate', 'cleanup', 'shutdown'] for event in events: result = node.transition(event) print('事件 %-10s -> 结果 %-7s, 当前状态 %s' % (event, result, node.state)) if __name__ == '__main__': main()
class LifecycleNode:把“工作站”抽象成一个类,对应一个生命周期节点。self.state = 'unconfigured'节点诞生即处于未配置状态,对应工作站刚开门。def on_configure(self):配置回调:加载参数、初始化硬件。def transition(self, event):模拟生命周期管理器的“事件分发”入口。if self.state == 'inactive' and event == 'activate':只有合法状态迁移才会执行对应回调。if event == 'shutdown':shutdown 在任何主状态都合法,类似“随时可以收工”。events = ['configure', 'activate', ...]用一条事件队列模拟外部管理器对节点的受控调度。if __name__ == '__main__':直接运行本文件即可看到完整的状态流转过程。
常见误区
误区launch 不就是把 `ros2 run` 的命令抄进一个文件里吗?我手动开终端一样能做到,没啥技术含量。
正解launch 远不止“批量执行命令”。它还能统一配置参数、重映射话题名、嵌套 include 子清单、声明命名空间、配合条件判断动态决定启动内容。它更像店长的“开店清单”而不只是“喊话列表”——清单里记录了食材、火候、顺序和应急预案,而喊话只传达了“开工”两个字。
为何常见:初学者从单节点 ros2 run 起步,第一次接触 launch 时看到的直观效果就是“一次启动了好几个节点”,于是把它简化理解成批处理。
误区我启动了生命周期节点,它应该立刻就开始发布数据、执行任务了吧?和普通节点一样。
正解生命周期节点启动后默认处于 unconfigured 状态,就像工作站开门了但锅碗还没摆。它必须依次经历 configure(配置)、activate(激活)两个转换事件,才会进入 active 状态真正干活。如果没做这两步,它就静静地“待命”,不发布任何核心数据。
为何常见:普通节点“启动即运行”的惯性思维太强,容易让人忽略生命周期节点多出来的状态层。
误区我加一个 `bool is_ready` 标志位,自己控制什么时候开始干活,不是更轻量吗?何必学生命周期。
正解自己写标志位的问题是“没有公共标准”。ROS2 的生命周期是一套被所有工具链认可的协议:ros2 lifecycle 命令能查、可视化工具能读、上层调度系统能统一管理。就像两块状态牌,一块是店里自制的“小纸条”,一块是行业统一的“标准标牌”,后者才能让所有部门都看懂。
为何常见:小节点确实用不上生命周期,做过一两个简单项目后容易形成“用不上”的错觉,忽略了它在大系统中的协调价值。
误区组合节点嘛,就是把几个 launch 文件放进一个文件里一起启动,省得分开敲命令。
正解组合节点指的是把多个节点加载进同一个进程运行,关键在于“单进程”而非“单文件”。合并 launch 文件只是把启动描述放到一起,节点仍然是各自独立的进程,通信开销一点没省。就好比拼桌吃饭不叫共用一个厨房,只有共享灶台、水池、冰箱才算。
为何常见:“组合”这个词容易让人联想到文件组合,而忽略了它真正的技术内核是进程级合并。
误区on_configure 里如果初始化失败,节点应该会进入 finalized 或者干脆退出,这样才安全。
正解恰恰相反,回调返回失败意味着“这次迁移被拒绝”,节点会停留在原状态,等待开发者排查后重新触发。这保证了系统的确定性:状态不会因为一次失败就跳到不可预期的位置。就像灶台点不着火,厨房不会直接宣布倒闭,而是保持原状、等师傅来修。
为何常见:把“失败”等同于“系统终止”是日常经验的直觉,但在状态机设计中,“拒绝迁移、保持原地”才是更安全的默认策略。
面试题
Q1. launch 文件最主要的作用是什么?
- A.编译ROS2功能包
- B.批量启动节点配置参数← 正确答案
- C.创建机器人三维模型
- D.打包发布量产机器人
答案:B。launch 文件的核心是“批量启动节点并统一配置参数”,相当于餐厅的开店清单。编译功能包是 colcon build 的职责,创建三维模型属于 URDF/可视化范畴,打包发布则是发布流程的事,都与 launch 无关。
Q2. ROS2 官方推荐用哪种方式编写 launch 文件?
- A.用 Python 编写清单← 正确答案
- B.用 XML 编写清单
- C.用 C++ 编写清单
- D.用 JSON 编写清单
答案:A。ROS2 官方默认推荐 Python 编写 launch,因为它支持循环、条件、变量替换,表达力更强;XML 虽被兼容但属于“表格单据”式写法,灵活性不足;C++ 和 JSON 都不是 ROS2 的 launch 编写格式。
Q3. 生命周期节点刚启动后,默认处于哪个状态?
- A.立即进入已激活状态
- B.处于未配置状态待配置← 正确答案
- C.直接进入已终结状态
- D.随机进入任意一个状态
答案:B。生命周期节点启动后默认处于 unconfigured(未配置),就像工作站刚开门,不能直接干活,必须先后经历 configure 和 activate 才能进入工作状态。普通节点才是“启动即运行”,这是两类节点的关键区别。
Q4. Python launch 中,generate_launch_description() 的返回值是什么?
- A.节点启动描述对象
- B.启动描述集合对象← 正确答案
- C.参数配置字段对象
- D.命令终端字符串对象
答案:B。该函数返回 LaunchDescription 对象,它封装了本次启动的全部动作(节点、参数、嵌套等),相当于店长手里的“最终开店清单”。单个节点、单个参数只是清单中的条目,不是返回值本身。
Q5. 生命周期节点从未激活变为已激活,需要触发哪个转换事件?
- A.执行 configure 操作
- B.执行 activate 操作← 正确答案
- C.执行 cleanup 操作
- D.执行 shutdown 操作
答案:B。activate 事件负责把节点从 inactive 带到 active,触发 on_activate 回调后节点才开始发布数据。configure 是“未配置到未激活”用的,cleanup 是“回到未配置”用的,shutdown 则通向终结状态。
Q6. 用 XML 写 launch 时,声明启动一个节点应该用哪个标签?
- A.使用 launch 根标签
- B.使用 node 标签声明节点← 正确答案
- C.使用 param 标签配置参数
- D.使用 group 标签分组管理
答案:B。XML launch 中 <node> 标签专门用于声明节点,用 pkg 和 exec 属性指定包名与程序名;<launch> 是根标签起包裹作用,<param> 用于配置参数,<group> 用于分组,三者都不能替代 <node> 的启动职责。
Q7. 关于组合节点,下列说法正确的是?
- A.多个节点组合成一个进程← 正确答案
- B.多个文件组合成一个包
- C.多个包组合成一个工作区
- D.多个进程组合成一台主机
答案:A。组合节点的本质是“多个节点共享一个进程”,通过进程容器加载,节点间走进程内通信。B 描述的是文件组织,C 是工作区叠加概念,D 是分布式部署,都与组合节点的“单进程”内核不符。
Q8. launch 文件可以完成以下哪些任务?
- A.设置节点的运行参数← 正确答案
- B.对节点名称进行重映射← 正确答案
- C.嵌套引入其他的清单← 正确答案
- D.修改系统内核调度策略
答案:ABC。设置参数(parameters)、重映射话题与节点名(remapping)、嵌套引入其他 launch(include)都是 launch 的核心能力;而修改内核调度策略属于操作系统层操作,与 launch 无关。
Q9. 关于生命周期回调,下列说法正确的有哪些?
- A.配置回调负责加载参数← 正确答案
- B.激活前不会发布核心数据← 正确答案
- C.回调失败会自动终结节点
- D.清理回调把节点带回未配置← 正确答案
答案:ABD。on_configure 负责加载参数与初始化硬件(A 对);节点只有进入 active 才开始对外发布核心数据(B 对);on_cleanup 把节点从 inactive/active 带回 unconfigured(D 对)。C 错:回调失败时迁移被拒绝,节点停留在原状态,就像灶台点不着火不会直接宣布餐厅倒闭。
Q10. 下列哪些属于合法的生命周期状态转换?
- A.未激活转换到已激活← 正确答案
- B.已激活转换到未配置← 正确答案
- C.未配置直接跳到已激活
- D.已激活回退到未激活← 正确答案
答案:ABD。A 是 activate(未激活→已激活),B 是 cleanup(已激活→未配置,ROS2 中合法),D 是 deactivate(已激活→未激活),三者都合法。C 非法:未配置必须先 configure 到未激活,再 activate 到已激活,不能跳过配置阶段直接激活——就像工作站还没备好料就开火是绝对不行的。