监控参数变化(Python)
目标: 学习使用 ParameterEventHandler 类来监控并响应参数变化。
教程级别: 中级
预计用时: 20 分钟
节点(node)通常需要响应自身参数或其他节点参数的变化。ParameterEventHandler 类可以方便地监听参数变化,让你的代码能够及时做出响应。本教程将展示如何使用 Python 版本的 ParameterEventHandler 类来监控节点自身参数的变化以及其他节点参数的变化。
在开始本教程之前,你应该首先完成以下教程:
在本教程中,你将创建一个新的功能包来容纳示例代码,编写 Python 代码来使用 ParameterEventHandler 类,并测试生成的代码。
1 创建功能包
Section titled “1 创建功能包”首先,打开一个新终端并 source 你的 ROS 2 安装环境,以便 ros2 命令能够正常使用。
按照说明创建一个名为 ros2_ws 的新工作空间。
请记住,功能包应该在 src 目录中创建,而不是工作空间的根目录。因此,切换到 ros2_ws/src 目录,然后在其中创建一个新的功能包:
$ 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 中。
1.1 更新 package.xml
Section titled “1.1 更新 package.xml”由于你在创建功能包时使用了 --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>2 编写 Python 节点
Section titled “2 编写 Python 节点”在 ros2_ws/src/python_parameter_event_handler/python_parameter_event_handler 目录中,创建一个名为 parameter_event_handler.py 的新文件,并将以下代码粘贴到其中:
import rclpyfrom rclpy.executors import ExternalShutdownExceptionfrom rclpy.node import Nodeimport 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): pass2.1 审查代码
Section titled “2.1 审查代码”顶部的 import 语句用于导入功能包依赖项。
import rclpyfrom rclpy.executors import ExternalShutdownExceptionfrom rclpy.node import Nodeimport 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): pass2.2 添加入口点
Section titled “2.2 添加入口点”打开 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', ],},3 构建和运行
Section titled “3 构建和运行”在构建之前,建议先在工作空间(ros2_ws)的根目录下运行 rosdep 来检查缺失的依赖项:
Linux
$ rosdep install -i --from-path src --rosdistro $ROS_DISTRO -ymacOS
rosdep 仅在 Linux 上运行,因此你可以跳到下一步。
Windows
rosdep 仅在 Linux 上运行,因此你可以跳到下一步。
切换回工作空间的根目录 ros2_ws,并构建你的新功能包:
$ colcon build --packages-select python_parameter_event_handler打开一个新终端,切换到 ros2_ws,并 source setup 文件:
Linux / macOS
$ . install/setup.bashWindows
$ call install\setup.bat现在运行节点:
$ ros2 run python_parameter_event_handler node_with_parameters该节点现在已激活,拥有一个参数,每当此参数更新时都会打印一条消息。要测试这一点,请打开另一个终端,像之前一样 source ROS setup 文件,然后执行以下命令:
$ ros2 param set node_with_parameters an_int_param 43运行节点的终端将显示类似以下的消息:
[INFO] [1698483083.315084660] [node_with_parameters]: Received an update to parameter: an_int_param: 43我们之前在节点中设置的回调已被调用,并显示了更新后的新值。现在可以在终端中按 ^C 终止正在运行的 parameter_event_handler 示例。
到目前为止,我们构建并测试了一个小节点,用于监控节点自身拥有的单个参数。以该节点为基础,下面介绍 ParameterEventHandler 在另外两种场景中的用法。
监控另一个节点的参数变化
Section titled “监控另一个节点的参数变化”你还可以使用 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,并像之前一样构建更新后的功能包:
$ colcon build --packages-select python_parameter_event_handler然后 source setup 文件:
Linux / macOS
$ . install/setup.bashWindows
$ call install\setup.bat现在,要测试远程参数的监控,首先运行新构建的 parameter_event_handler 代码:
$ ros2 run python_parameter_event_handler node_with_parameters接下来,从另一个终端(已初始化 ROS)运行 parameter_blackboard 演示程序,如下所示:
$ ros2 run demo_nodes_cpp parameter_blackboard最后,从第三个终端(已初始化 ROS),在 parameter_blackboard 节点上设置一个参数:
$ ros2 param set parameter_blackboard a_double_param 3.45执行此命令后,你应该在 parameter_event_handler 窗口中看到输出,表明回调函数在参数更新时被调用:
[INFO] [1699821958.757770223] [node_with_parameters]: Received an update to parameter: a_double_param: 3.45同时监控所有节点参数
Section titled “同时监控所有节点参数”如果你需要同时监控多个节点或参数,为每个参数都调用一次 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,并像之前一样重新构建更新后的功能包:
$ colcon build --packages-select python_parameter_event_handler然后 source setup 文件:
Linux / macOS
$ . install/setup.bashWindows
$ call install\setup.bat要测试新的事件回调,首先运行 parameter_event_handler 节点:
$ ros2 run python_parameter_event_handler node_with_parameters然后,从第二个终端(已 source ROS),设置原始的 int 参数:
$ ros2 param set node_with_parameters an_int_param 44执行此命令后,你应该看到单参数回调和事件回调同时被触发:
[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 参数:
$ ros2 param set node_with_parameters another_double_param 4.4由于没有为 double 参数添加单参数回调(通过 add_parameter_callback),我们应该只看到事件回调被触发:
[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 教程。