管理节点简介
管理机器人操作系统(ROS)框架内节点的生命周期对于创建稳健可靠的机器人应用至关重要。在ROS 1时期一个常见的抱怨是缺乏对节点生命周期的控制。例如,传感器读取器节点在传感器驱动节点准备就绪之前就开始操作的情景。这种情况可能导致系统错误。ROS 2通过一种被称为管理节点(Managed Nodes)的优雅解决方案来解决这一问题,引入生命周期(Life Cycle)管理的结构化方法,增强了机器人应用的稳定性和可预测性。
在ROS 2中,管理节点以C++实现(Python支持待定),这是确保开发者拥有构建更可靠、更易维护系统所需工具的战略举措。被管理的节点是从rclcpp_lifecycle::LifecycleNode
而不是标准的rclcpp::Node
派生的。这种派生为它们带来了核心的生命周期管理能力。通过这种机制,开发者现在可以编程节点按照指定的生命周期状态行为,如配置、激活、停用、清理和关闭等。
将管理节点引入ROS 2为机器人开发社区带来了几个好处。
首先,它允许更确定性的初始化和关闭过程。开发者可以确保正确管理依赖关系,并以尊重这些依赖的顺序启动节点,从而避免启动错误,提高系统稳定性。例如,可以配置传感器驱动节点始终在任何依赖的传感器读取器节点之前启动,确保在需要时可以获取传感器数据。
其次,管理节点提供的详细生命周期管理有助于更好的错误处理和恢复。由于每个节点的状态都是明确管理的,因此当节点遇到问题时,更容易检测并将其转移到恢复状态或根据需要重启。这在需要快速从故障中恢复无需人工干预的场景中非常宝贵。
此外,管理节点使得更高效的资源使用成为可能。通过按需激活和停用节点,可以在不需要时节约资源,这在对电力敏感的应用或需要大量计算资源运行复杂模拟的场景中特别有用。
尽管有明显的优势,过渡到管理节点并在ROS 2中采用生命周期管理实践也带来了挑战。开发者需要熟悉生命周期状态机,并了解如何设计他们的节点以遵循新模型。此外,管理节点目前仅在C++中完全实现的局限性意味着,构成ROS社区重要部分的Python开发者正在等待完全支持。
管理节点状态机
管理节点状态机由一系列定义明确的状态组成,这些状态分为主状态,次级状态和转换状态。每个状态都有特定的角色和目的,确保节点生命周期的每个阶段都可以被精确控制和监测。
主状态 – 稳定状态
在管理节点状态机中,主状态代表节点的稳定状态,即节点在其生命周期中大部分时间所处的状态。这些状态包括:
- 未配置(Unconfigured):节点刚刚被创建,尚未进行配置。
- 非活动(Inactive):节点已经配置完成,但尚未激活。在这个状态下,节点处于一种待命模式,准备好随时进入活动状态。
- 活动(Active):节点已经被激活,正在执行其主要功能。
- 终结(Finalized):节点已经完成其生命周期,不再参与任何活动。
次级状态 – 过渡状态
次级状态,或称为过渡状态,是节点从一个主状态转换到另一个主状态时的中间状态。这些状态是临时的,主要目的是处理状态之间的过渡。包括:
- 配置中(Configuring):从未配置状态过渡到已配置状态的过程。
- 清理中(CleaningUp):从已配置状态回到未配置状态的过程。
- 激活中(Activating):从已配置状态过渡到活动状态的过程。
- 停用中(Deactivating):从活动状态回到已配置状态的过程。
- 关闭中(ShuttingDown):节点从任何状态进入关闭状态的过程。
- 错误处理(ErrorProcessing):处理和清除错误状态的过程。
转换状态 – 触发状态变化
状态转换是节点从一个状态移动到另一个状态的触发机制。这些转换可以通过多种方式触发,包括用户通过启动文件、管理节点或命令行接口(CLI)服务调用。每次转换不仅代表了节点状态的变化,也反映了节点在其生命周期中所经历的不同阶段。有效地管理这些转换对于确保节点能够可靠地执行其设计功能至关重要。
状态详细介绍
1. 主要状态:未配置(Unconfigured)
这是节点在实例化后立即所处的生命周期状态。 这也是节点在发生错误后可以返回的状态。 在这种状态下,预计不会有存储状态。
有效转出未配置状态:
- 节点可以通过配置转换转换到非活动(
Inactive
)状态。 - 节点可以通过关闭转换转换到关闭(
Finalized
)状态。
2. 主要状态:非活动(Inactive)
该状态表示当前未执行任何处理的节点。此状态的主要目的是允许(重新)配置节点(更改配置参数、添加和删除主题发布/订阅等),而无需更改其运行时的行为。
在此状态下,节点将不会接收任何执行时间来读取主题、执行数据处理、响应功能服务请求等。到达托管主题的任何数据都不会被读取和/或处理。 数据保留将遵循为该主题配置的 QoS 策略。另外,对处于非活动状态的节点的任何托管服务请求都不会得到答复(对于调用者,它们将立即失败)。
有效转出非活动状态:
- 节点可以通过关闭转换(
shutdown transition
)转换到终结(Finalized
)状态。 - 节点可以通过清理转换(
cleanup transition
)转换到未配置(Unconfigured
)状态。 - 节点可以通过激活转换(
activate transition
)转换到活动(Active
)状态。
3. 主要状态:活动(Active)
这是节点生命周期的主要状态。 在此状态下,节点执行任何处理、响应服务请求、读取和处理数据、产生输出等。如果在此状态下发生节点/系统无法处理的错误,则节点将转换为 ErrorProcessing
。
有效转出活动状态:
- 节点可以通过停用转换(
deactivate transition
)转换到不活动(Inactive
)状态。 - 节点可以通过关闭转换(
shutdown transition
)转换到终结(Finalized
)状态。
4. 主要状态:终结(Finalized)
终结(Finalized
)状态是节点在被销毁之前立即结束的状态。 该状态始终是终止状态,从这里开始的唯一转换就是被销毁。
这种状态的存在是为了支持调试和自省。 发生故障的节点对于系统自省仍然可见,并且可能可以通过调试工具进行自省,而不是直接破坏。 如果节点在重生循环中启动或已知循环原因,则预计监管进程将制定自动销毁并重新创建该节点的策略。
有效转出终结状态:
- 可以通过销毁转换(
destroy transition
)来释放节点。
5. 过渡状态:配置中(Configuring)
在此转换状态下,将调用节点的 onConfigure
回调,以允许节点加载其配置并进行任何所需的设置。
节点的配置通常涉及在节点生命周期内必须执行一次的任务,例如获取永久内存缓冲区和设置不更改的主题发布/订阅。节点使用它来设置它在其整个生命周期中必须持有的任何资源(无论它是活动的还是非活动的)。 作为示例,此类资源可以包括主题发布和订阅、连续保存的存储器以及初始化配置参数。
有效转出配置中状态:
- 如果
onConfigure
回调成功,节点将转换为Inactive
状态。 - 如果
onConfigure
回调导致失败代码,则节点将转换回Unconfigured
状态。 - 如果
onConfigure
回调引发或导致任何其他结果代码,则节点将转换为ErrorProcessing
6. 过渡状态:清理中(CleaningUp)
在此转换状态下,将调用节点的回调 onCleanup
。 此方法预计会清除所有状态并将节点返回到与首次创建时功能等效的状态。 如果清理无法成功实现,它将转换为 ErrorProcessing
。
有效转出清理中状态:
- 如果
onCleanup
回调成功,节点将转换为Unconfigured
。 - 如果
onCleanup
回调引发或导致任何其他返回代码,则节点将转换为ErrorProcessing
。
7. 过渡状态:激活中(Activating)
在此转换状态下,将执行回调 onActivate
。 该方法预计会为开始执行做任何最后的准备。 这可能包括获取仅在节点实际活动时保留的资源,例如对硬件的访问。 理想情况下,在此回调中不应执行需要大量时间的准备工作(例如冗长的硬件初始化)。
有效转出激活中状态:
- 如果
onActivate
回调成功,节点将转换Active
为状态。 - 如果
onActivate
回调引发或导致任何其他返回代码,则节点将转换为ErrorProcessing
。
8. 过渡状态:停用中(Deactivating)
在此转换状态下,将执行回调 onDeactivate
。 此方法预计会进行任何清理以开始执行,并且应反转 onActivate
更改。
有效转出停用中状态:
- 如果
onDeactivate
回调成功,节点将转换为Inactive
状态。 - 如果
onDeactivate
回调引发或导致任何其他返回代码,则节点将转换为ErrorProcessing
。
9. 过渡状态:关闭中(ShuttingDown)
在此转换状态下,将执行回调 onShutdown
。 该方法预计会在销毁之前进行任何必要的清理。 它可以从除 Finalized
之外的任何主要状态进入,原始状态将传递给该方法。
有效转出关闭中状态:
- 如果
onShutdown
回调成功,节点将转换为Finalized
。 - 如果
onShutdown
回调引发或导致任何其他返回代码,则节点将转换为ErrorProcessing
。
10. 过渡状态:错误处理(ErrorProcessing)
在这种过渡状态下,任何错误都可以被清除。 可以从执行用户代码的任何状态进入此状态。 如果错误处理成功完成,节点可以返回到 Unconfigured
状态,如果不可能进行完全清理,则节点必须失败,并且节点将转换到 Finalized
状态,为销毁做准备。
转换到 ErrorProcessing
可能是由回调中的错误返回代码以及回调中的方法或未捕获的异常引起的。
有效转出错误处理状态:
- 如果
onError
回调成功,节点将转换为Unconfigured
。 预期onError
将清除任何先前状态的所有状态。 因此,如果从Active
输入,它必须提供onDeactivate
和onCleanup
的清理才能返回成功。 - 如果
onShutdown
回调引发或导致任何其他结果代码,则节点将转换为Finalized
。
11. 销毁过渡(Destroy Transition)
这种转变只会导致节点的释放。 在面向对象的环境中,它可能只涉及调用析构函数。 否则它将调用标准的释放方法。 这种转变应该总是成功的。
12. 创建过渡(Create Transition)
此转换将实例化节点,但不会运行构造函数之外的任何代码。
管理接口
被管理节点将通过以下接口暴露给 ROS 生态系统,如执行管理的工具所见。 该接口不应受到生命周期状态所施加的通信限制。
常见的模式是拥有一个容器类,它从库加载托管节点实现,并通过插件架构自动通过方法公开所需的管理接口,并且容器不受生命周期管理的约束。 然而,考虑任何提供此接口并遵循受管节点生命周期策略的实现是完全有效的。 相反,任何提供这些服务但不按照生命周期状态机中定义的方式运行的对象都是格式错误的。
除了公开的 ROS 消息和主题/服务(用于远程管理)之外,这些服务还可以通过属性和方法调用(用于本地管理)提供。 在提供ROS中间件接口的情况下,必须使用特定的主题,并且应将它们放置在合适的命名空间中。
除了 create
之外,每个可能的监管转换都将通过转换名称作为服务提供。 create
将需要一个额外的参数来查找要实例化的节点。 该服务将报告转换是否成功完成。
创建生命周期节点(Lifecycle Node)将会暴露一些标准的接口,示例如下。
标准服务接口
生命周期节点暴露了以下标准服务接口,通过它们,用户和其他节点可以与生命周期节点交互,实现对其状态的精细控制:
- <ns>/change_state:这个服务接口允许调用者请求节点执行一个合法的状态转换。例如,从非活动状态(Inactive)转换到活动状态(Active),或相反。
- <ns>/get_available_transitions:此服务显示节点当前状态可以执行的合法转换。这对于理解节点在其生命周期中的可能路径非常有用。
- <ns>/get_state:通过调用此服务,可以查询节点当前所处的状态,帮助外部实体了解节点的运行状况。
- <ns>/get_available_states:此服务提供节点所有可能达到的状态的列表,为理解整个状态机提供了框架。
- <ns>/get_transition_graph:此服务展示了节点的完整状态机,包括所有状态和可能的转换。这是理解和分析节点行为的强大工具。
标准话题接口
除了服务接口外,生命周期节点还通过一个标准话题接口与外界通信:
- <ns>/<node_name>__transition_event:当节点的状态发生转换时,该节点会在此话题上发布消息。这允许外部监视器实时跟踪节点状态的变化,对于调试和监控系统行为至关重要。
生命周期命令行工具
节点的生命周期通过命令行工具 ros2 lifecycle
来进行管理。ros2 lifecycle
命令是一个强大的工具,它允许用户执行一系列操作来查询和控制生命周期节点的状态。以下是一些基本用法:
ros2 lifecycle nodes
:此命令列出系统中所有的生命周期节点(LC nodes)。这是了解当前系统配置的起点,特别是在复杂系统中,能够迅速识别出所有使用了生命周期管理的节点。ros2 lifecycle get <?node>
:如果指定了节点名称(node),这个命令会列出该特定节点的当前状态。如果没有指定节点,则列出所有生命周期节点的当前状态。这对于实时监控节点状态变化或系统诊断来说是一个非常有用的功能。ros2 lifecycle list <node>
:对于指定的节点,此命令列出可能的下一个状态及相应的转换调用(包括名称和ID)。这为管理节点提供了必要的信息,以决定应该执行哪个状态转换。ros2 lifecycle set <node> <transition>
:此命令触发指定生命周期节点上的一个转换,可以通过名称或ID指定转换。这是控制节点进入新状态的直接方式,对于自动化脚本或手动管理节点状态特别有用。
通过启动文件(Launch File)管理节点生命周期
在ROS 2中,Python启动文件(Launch Files)提供了一种灵活的方式来管理和配置生命周期节点(LC节点),通过内置的LC API,用户可以在系统启动时自动化节点的状态转换和管理。这一机制不仅简化了节点管理过程,也为复杂系统的部署和维护提供了极大的便利。
定义生命周期节点:launch_ros.actions.LifecycleNode(..)
使用launch_ros.actions.LifecycleNode(..)
,开发者可以在启动文件中定义一个生命周期节点。这个函数允许用户指定节点的各种参数,包括节点名称、命名空间、包名和执行的节点类型,这为节点的初始化和配置提供了基础。
监听状态转换:launch_ros.event_handlers.OnStateTransition(..)
通过launch_ros.event_handlers.OnStateTransition(..)
,启动文件可以设置在生命周期节点从一个状态转换到另一个状态时执行的动作。这个功能极大地增强了系统的灵活性和响应能力,使得开发者可以根据节点的状态变化来触发特定的操作,如启动另一个节点、记录日志或发送通知等。
触发状态转换事件:launch.actions.EmitEvent(..)
launch.actions.EmitEvent(..)
结合launch_ros.events.lifecycle.ChangeState(..)
使用,允许在启动文件中直接触发生命周期节点的状态转换事件。通过选择一个特定的转换,开发者可以精确地控制节点应当进入的目标状态,从而实现对节点行为的精细管理。
状态和转换枚举:lifecycle_msgs.msg
lifecycle_msgs.msg
中包含了状态和转换的枚举类型,这些枚举为启动文件中的状态管理提供了必要的参数。通过使用这些枚举值,开发者可以清晰地指定节点应该执行的状态转换,确保节点行为的正确性和预期性。
总结
ROS 2中的管理节点标志着向更可靠、可维护和高效的机器人系统开发迈出的一大步。通过解决与ROS 1相关的节点生命周期管理的不足,ROS 2为创建复杂、有韧性的应用打开了新的可能性,这些应用更适应现代机器人技术的需求。ROS 2的Python启动文件通过提供一系列LC API,极大地简化了生命周期节点的管理流程。这些API不仅使节点的状态管理更加直观和易于操作,还为自动化系统配置和管理提供了强有力的工具。