Skip to content

监控参数变化(Python)

目标: 学习使用 ParameterEventHandler 类来监控并响应参数变化。

教程级别: 中级

预计用时: 20 分钟

节点(node)通常需要响应自身参数或其他节点参数的变化。ParameterEventHandler 类可以方便地监听参数变化,让你的代码能够及时做出响应。本教程将展示如何使用 Python 版本的 ParameterEventHandler 类来监控节点自身参数的变化以及其他节点参数的变化。

在开始本教程之前,你应该首先完成以下教程:

在本教程中,你将创建一个新的功能包来容纳示例代码,编写 Python 代码来使用 ParameterEventHandler 类,并测试生成的代码。

首先,打开一个新终端并 source 你的 ROS 2 安装环境,以便 ros2 命令能够正常使用。

按照说明创建一个名为 ros2_ws 的新工作空间。

请记住,功能包应该在 src 目录中创建,而不是工作空间的根目录。因此,切换到 ros2_ws/src 目录,然后在其中创建一个新的功能包:

Terminal window
$ ros2 pkg create --build-type ament_python --license Apache-2.0 python_parameter_event_handler --dependencies rclpy

终端将返回一条消息,确认功能包 python_parameter_event_handler 已创建,并列出所有必要的文件和文件夹。

--dependencies 参数会自动将必要的依赖项添加到 package.xml 和 CMakeLists.txt 中。

由于你在创建功能包时使用了 --dependencies 选项,因此无需手动向 package.xml 添加依赖项。不过,和往常一样,请确保在 package.xml 中填写描述、维护者邮箱和姓名以及 license 信息。

<description>Python parameter events client tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache-2.0</license>

在 ros2_ws/src/python_parameter_event_handler/python_parameter_event_handler 目录中,创建一个名为 parameter_event_handler.py 的新文件,并将以下代码粘贴到其中:

import rclpy
from rclpy.executors import ExternalShutdownException
from rclpy.node import Node
import rclpy.parameter
from rclpy.parameter_event_handler import ParameterEventHandler
class SampleNodeWithParameters(Node):
def __init__(self):
super().__init__('node_with_parameters')
self.declare_parameter('an_int_param', 0)
self.handler = ParameterEventHandler(self)
self.callback_handle = self.handler.add_parameter_callback(
parameter_name="an_int_param",
node_name="node_with_parameters",
callback=self.callback,
)
def callback(self, p: rclpy.parameter.Parameter) -> None:
self.get_logger().info(f"Received an update to parameter: {p.name}: {rclpy.parameter.parameter_value_to_python(p.value)}")
def main():
try:
with rclpy.init():
node = SampleNodeWithParameters()
rclpy.spin(node)
except (KeyboardInterrupt, ExternalShutdownException):
pass

顶部的 import 语句用于导入功能包依赖项。

import rclpy
from rclpy.executors import ExternalShutdownException
from rclpy.node import Node
import rclpy.parameter
from rclpy.parameter_event_handler import ParameterEventHandler

接下来的一段代码创建了 SampleNodeWithParameters 类及其构造函数。该类的构造函数声明了一个整数参数 an_int_param,默认值为 0。然后,代码创建了一个 ParameterEventHandler 对象,用于监控参数的变化。

class SampleNodeWithParameters(Node):
def __init__(self):
super().__init__('node_with_parameters')
self.declare_parameter('an_int_param', 0)
self.handler = ParameterEventHandler(self)

最后,我们添加一个参数回调,并保存新回调的句柄。

注意:保存 add_parameter_callback 返回的句柄非常重要;否则,回调将无法正确注册。

self.callback_handle = self.handler.add_parameter_callback(
parameter_name="an_int_param",
node_name="node_with_parameters",
callback=self.callback,
)

回调函数使用的是 SampleNodeWithParameters 类的 callback 方法。

def callback(self, p: rclpy.parameter.Parameter) -> None:
self.get_logger().info(f"Received an update to parameter: {p.name}: {rclpy.parameter.parameter_value_to_python(p.value)}")

在 SampleNodeWithParameters 之后是一个典型的 main 函数,它初始化 ROS,然后 spin 示例节点使其能够发送和接收消息,最后在用户于控制台按下 ^C 后关闭。

def main():
try:
with rclpy.init():
node = SampleNodeWithParameters()
rclpy.spin(node)
except (KeyboardInterrupt, ExternalShutdownException):
pass

打开 setup.py 文件。同样,将 maintainer、maintainer_email、description 和 license 字段与你的 package.xml 保持一致:

maintainer='YourName',
maintainer_email='you@email.com',
description='Python parameter tutorial',
license='Apache-2.0',

在 entry_points 字段的 console_scripts 括号内添加以下行:

entry_points={
'console_scripts': [
'node_with_parameters = python_parameter_event_handler.parameter_event_handler:main',
],
},

在构建之前,建议先在工作空间(ros2_ws)的根目录下运行 rosdep 来检查缺失的依赖项:

Linux

Terminal window
$ rosdep install -i --from-path src --rosdistro $ROS_DISTRO -y

macOS

rosdep 仅在 Linux 上运行,因此你可以跳到下一步。

Windows

rosdep 仅在 Linux 上运行,因此你可以跳到下一步。

切换回工作空间的根目录 ros2_ws,并构建你的新功能包:

Terminal window
$ colcon build --packages-select python_parameter_event_handler

打开一个新终端,切换到 ros2_ws,并 source setup 文件:

Linux / macOS

Terminal window
$ . install/setup.bash

Windows

Terminal window
$ call install\setup.bat

现在运行节点:

Terminal window
$ ros2 run python_parameter_event_handler node_with_parameters

该节点现在已激活,拥有一个参数,每当此参数更新时都会打印一条消息。要测试这一点,请打开另一个终端,像之前一样 source ROS setup 文件,然后执行以下命令:

Terminal window
$ ros2 param set node_with_parameters an_int_param 43

运行节点的终端将显示类似以下的消息:

Terminal window
[INFO] [1698483083.315084660] [node_with_parameters]: Received an update to parameter: an_int_param: 43

我们之前在节点中设置的回调已被调用,并显示了更新后的新值。现在可以在终端中按 ^C 终止正在运行的 parameter_event_handler 示例。

到目前为止,我们构建并测试了一个小节点,用于监控节点自身拥有的单个参数。以该节点为基础,下面介绍 ParameterEventHandler 在另外两种场景中的用法。

你还可以使用 ParameterEventHandler 来监控另一个节点参数的变化。我们来更新 SampleNodeWithParameters 类来监控另一个节点中参数的变化。我们将使用 parameter_blackboard 演示程序来托管一个 double 参数,并监控其更新。

首先更新构造函数,在现有代码之后添加以下代码:

def __init__(...):
...
self.callback_handle2 = self.handler.add_parameter_callback(
parameter_name="a_double_param",
node_name="parameter_blackboard",
callback=self.callback,
)

在终端中,切换回工作空间的根目录 ros2_ws,并像之前一样构建更新后的功能包:

Terminal window
$ colcon build --packages-select python_parameter_event_handler

然后 source setup 文件:

Linux / macOS

Terminal window
$ . install/setup.bash

Windows

Terminal window
$ call install\setup.bat

现在,要测试远程参数的监控,首先运行新构建的 parameter_event_handler 代码:

Terminal window
$ ros2 run python_parameter_event_handler node_with_parameters

接下来,从另一个终端(已初始化 ROS)运行 parameter_blackboard 演示程序,如下所示:

Terminal window
$ ros2 run demo_nodes_cpp parameter_blackboard

最后,从第三个终端(已初始化 ROS),在 parameter_blackboard 节点上设置一个参数:

Terminal window
$ ros2 param set parameter_blackboard a_double_param 3.45

执行此命令后,你应该在 parameter_event_handler 窗口中看到输出,表明回调函数在参数更新时被调用:

Terminal window
[INFO] [1699821958.757770223] [node_with_parameters]: Received an update to parameter: a_double_param: 3.45

如果你需要同时监控多个节点或参数,为每个参数都调用一次 add_parameter_callback 会很繁琐。在这种情况下,你可以使用 add_parameter_event_callback 来注册一个回调,当任何节点的任何参数发生变化时都会触发该回调。

为此,首先更新 SampleNodeWithParameters 构造函数,添加以下代码:

def __init__(...):
self.declare_parameter("another_double_param", 0.0)
...
self.event_calback_handle = self.handler.add_parameter_event_callback(
callback=self.event_callback,
)

这里声明了一个新的 double 参数 another_double_param,并添加了一个事件回调来同时监控两个参数。事件回调的签名与常规的单参数回调不同,因此我们还需要定义一个合适的回调:

def event_callback(self, parameter_event):
self.get_logger().info(f"Received parameter event from node {parameter_event.node}")
for p in parameter_event.changed_parameters:
self.get_logger().info(
f"Inside event: {p.name} changed to: {rclpy.parameter.parameter_value_to_python(p.value)}"
)

请注意,parameter_event 的类型为 rcl_interfaces/msg/ParameterEvent。虽然本教程中没有展示,但事件回调也可用于监控参数的添加或删除。

切换回工作空间的根目录 ros2_ws,并像之前一样重新构建更新后的功能包:

Terminal window
$ colcon build --packages-select python_parameter_event_handler

然后 source setup 文件:

Linux / macOS

Terminal window
$ . install/setup.bash

Windows

Terminal window
$ call install\setup.bat

要测试新的事件回调,首先运行 parameter_event_handler 节点:

Terminal window
$ ros2 run python_parameter_event_handler node_with_parameters

然后,从第二个终端(已 source ROS),设置原始的 int 参数:

Terminal window
$ ros2 param set node_with_parameters an_int_param 44

执行此命令后,你应该看到单参数回调和事件回调同时被触发:

Terminal window
[INFO] [1746414766.240101027] [node_with_parameters]: Received an update to parameter: an_int_param: 44
[INFO] [1746414766.243499816] [node_with_parameters]: Received parameter event from node /node_with_parameters
[INFO] [1746414766.244271445] [node_with_parameters]: Inside event: an_int_param changed to: 4

现在设置新的 double 参数:

Terminal window
$ ros2 param set node_with_parameters another_double_param 4.4

由于没有为 double 参数添加单参数回调(通过 add_parameter_callback),我们应该只看到事件回调被触发:

Terminal window
[INFO] [1746414962.604832196] [node_with_parameters]: Received parameter event from node /node_with_parameters
[INFO] [1746414962.607429035] [node_with_parameters]: Inside event: another_double_param changed to: 4.4

注意:当一次设置多个参数时,最好使用 set_parameters_atomically,相关说明请参阅 About Parameters。这样,事件回调只会被触发一次。

你创建了一个带有参数的节点,并使用 ParameterEventHandler 类设置了回调来监控该参数的变化。你还使用同一个类来监控远程节点的变化,以及在单个事件回调中监控所有参数。ParameterEventHandler 是一种监控参数变化的便捷方式,让你能够对更新后的值做出响应。

要了解如何将 ROS 1 参数文件适配为 ROS 2,请参阅 Migrating YAML parameter files from ROS 1 to ROS 2 教程。