监控参数变化(C++)
目标: 学习使用 ParameterEventHandler 类来监控和响应参数变化。
教程级别: 中级
预计用时: 20 分钟
节点通常需要响应自身参数或其他节点参数的变化。ParameterEventHandler 类让监听参数变化变得非常简单,使你的代码能够对这些变化做出响应。本教程将展示如何使用 C++ 版本的 ParameterEventHandler 类来监控节点自身参数以及其他节点参数的变化。
在开始本教程之前,你应该先完成以下教程:
在本教程中,你将创建一个新的功能包来容纳示例代码,编写 C++ 代码来使用 ParameterEventHandler 类,并测试生成的代码。
1 创建一个功能包
Section titled “1 创建一个功能包”首先,打开一个新的终端并 source 你的 ROS 2 安装环境,以便 ros2 命令能够正常工作。
按照相关说明创建一个名为 ros2_ws 的新工作空间。
请记住,功能包应该在 src 目录中创建,而不是在工作空间的根目录下。因此,切换到 ros2_ws/src 目录,然后在其中创建一个新的功能包:
$ ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_parameter_event_handler --dependencies rclcpp终端将显示一条消息,确认功能包 cpp_parameter_event_handler 已创建,并列出所有必要的文件和文件夹。
--dependencies 参数会自动将必要的依赖项添加到 package.xml 和 CMakeLists.txt 中。
1.1 更新 package.xml
Section titled “1.1 更新 package.xml”由于创建功能包时使用了 --dependencies 选项,因此无需手动向 package.xml 或 CMakeLists.txt 中添加依赖项。不过,仍然需要确保在 package.xml 中填写描述、维护者姓名和邮箱以及 license 信息。
<description>C++ parameter events client tutorial</description><maintainer email="you@email.com">Your Name</maintainer><license>Apache-2.0</license>2 编写 C++ 节点
Section titled “2 编写 C++ 节点”在 ros2_ws/src/cpp_parameter_event_handler/src 目录中,创建一个名为 parameter_event_handler.cpp 的新文件,并将以下代码粘贴到其中:
#include <memory>
#include "rclcpp/rclcpp.hpp"
class SampleNodeWithParameters : public rclcpp::Node{public: SampleNodeWithParameters() : Node("node_with_parameters") { this->declare_parameter("an_int_param", 0);
// Create a parameter subscriber that can be used to monitor parameter changes // (for this node's parameters as well as other nodes' parameters) param_subscriber_ = std::make_shared<rclcpp::ParameterEventHandler>(this);
// Set a callback for this node's integer parameter, "an_int_param" auto cb = [this](const rclcpp::Parameter & p) { RCLCPP_INFO( this->get_logger(), "cb: Received an update to parameter \"%s\" of type %s: \"%ld\"", p.get_name().c_str(), p.get_type_name().c_str(), p.as_int()); }; cb_handle_ = param_subscriber_->add_parameter_callback("an_int_param", cb); }
private: std::shared_ptr<rclcpp::ParameterEventHandler> param_subscriber_; std::shared_ptr<rclcpp::ParameterCallbackHandle> cb_handle_;};
int main(int argc, char ** argv){ rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<SampleNodeWithParameters>()); rclcpp::shutdown();
return 0;}2.1 代码解析
Section titled “2.1 代码解析”第一条语句 #include 中的 <memory> 头文件提供了 std::make_shared 模板。随后,#include "rclcpp/rclcpp.hpp" 引入了 rclcpp 接口提供的各种功能,包括 ParameterEventHandler 类。
接下来,代码定义了一个类 SampleNodeWithParameters。该类的构造函数声明了一个整数参数 an_int_param,默认值为 0。然后,代码创建了一个 ParameterEventHandler,用于监控参数变化。最后,代码创建了一个 lambda 函数,并将其设置为每当 an_int_param 更新时触发的回调。
注意:务必保存
add_parameter_callback返回的句柄;否则,回调将无法正确注册。
SampleNodeWithParameters(): Node("node_with_parameters"){ this->declare_parameter("an_int_param", 0);
// Create a parameter subscriber that can be used to monitor parameter changes // (for this node's parameters as well as other nodes' parameters) param_subscriber_ = std::make_shared<rclcpp::ParameterEventHandler>(this);
// Set a callback for this node's integer parameter, "an_int_param" auto cb = [this](const rclcpp::Parameter & p) { RCLCPP_INFO( this->get_logger(), "cb: Received an update to parameter \"%s\" of type %s: \"%ld\"", p.get_name().c_str(), p.get_type_name().c_str(), p.as_int()); }; cb_handle_ = param_subscriber_->add_parameter_callback("an_int_param", cb);}在 SampleNodeWithParameters 之后是一个典型的 main 函数,它初始化 ROS,然后 spin 示例节点以使其能够发送和接收消息,最后在用户于控制台按下 ^C 时关闭。
int main(int argc, char ** argv){ rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<SampleNodeWithParameters>()); rclcpp::shutdown();
return 0;}2.2 添加可执行文件
Section titled “2.2 添加可执行文件”要编译此代码,首先打开 CMakeLists.txt 文件,并在依赖项 find_package(rclcpp REQUIRED) 下方添加以下代码行:
add_executable(parameter_event_handler src/parameter_event_handler.cpp)target_link_libraries(parameter_event_handler PUBLIC rclcpp::rclcpp)
install(TARGETS parameter_event_handler DESTINATION lib/${PROJECT_NAME})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 cpp_parameter_event_handler打开一个新的终端,切换到 ros2_ws,并 source setup 文件:
Linux / macOS
$ . install/setup.bashWindows
$ call install/setup.bat现在运行该节点:
$ ros2 run cpp_parameter_event_handler parameter_event_handler该节点现在已启动并持有一个参数,每当此参数更新时就会打印一条消息。要测试这一点,打开另一个终端,像之前一样 source ROS setup 文件(. install/setup.bash),然后执行以下命令:
$ ros2 param set node_with_parameters an_int_param 43运行该节点的终端将显示类似于以下内容的消息:
[INFO] [1606950498.422461764] [node_with_parameters]: cb: Received an update to parameter "an_int_param" of type integer: "43"我们之前在节点中设置的回调已被调用,并显示了更新后的新值。现在可以在终端中按 ^C 终止正在运行的 parameter_event_handler 示例。
到目前为止,我们编译并测试了一个小型节点,用于监控该节点自身的单个参数。下面以此节点为基础,介绍 ParameterEventHandler 在另外两种场景中的用法。
监控另一个节点的参数变化
Section titled “监控另一个节点的参数变化”你还可以使用 ParameterEventHandler 来监控另一个节点的参数变化。我们来更新 SampleNodeWithParameters 类,使其也监控另一个节点中某个参数的变化。我们将使用 parameter_blackboard 演示程序来托管一个 double 参数,并监控其更新。
首先更新构造函数,在现有代码之后添加以下代码:
// Now, add a callback to monitor any changes to the remote node's parameter. In this// case, we supply the remote node name.auto cb2 = [this](const rclcpp::Parameter & p) { RCLCPP_INFO( this->get_logger(), "cb2: Received an update to parameter \"%s\" of type: %s: \"%.02lf\"", p.get_name().c_str(), p.get_type_name().c_str(), p.as_double()); };auto remote_node_name = std::string("parameter_blackboard");auto remote_param_name = std::string("a_double_param");cb_handle2_ = param_subscriber_->add_parameter_callback(remote_param_name, cb2, remote_node_name);然后添加另一个成员变量 cb_handle2 来保存额外的回调句柄:
private: std::shared_ptr<rclcpp::ParameterEventHandler> param_subscriber_; std::shared_ptr<rclcpp::ParameterCallbackHandle> cb_handle_; std::shared_ptr<rclcpp::ParameterCallbackHandle> cb_handle2_; // Add this};在一个终端中,切换回工作空间的根目录 ros2_ws,像之前一样编译更新后的功能包:
$ colcon build --packages-select cpp_parameter_event_handler然后 source setup 文件:
Linux / macOS
$ . install/setup.bashWindows
$ call install/setup.bat现在,要测试远程参数的监控,首先运行新编译的 parameter_event_handler 代码:
$ ros2 run cpp_parameter_event_handler parameter_event_handler接下来,从另一个终端(已初始化 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] [1606952588.237531933] [node_with_parameters]: cb2: Received an update to parameter "a_double_param" of type: double: "3.45"同时监控所有节点参数
Section titled “同时监控所有节点参数”如果你需要同时监控多个节点或参数,逐一调用 add_parameter_callback 会非常繁琐。在这种情况下,你可以使用 add_parameter_event_callback 来注册一个单一的回调,它会在任何节点的任何参数发生变化时触发。
为此,首先更新 SampleNodeWithParameters 的构造函数以添加以下代码:
this->declare_parameter("another_double_param", 0.0);
...
auto event_cb = [this](const rcl_interfaces::msg::ParameterEvent & parameter_event) { RCLCPP_INFO( this->get_logger(), "Received parameter event from node \"%s\"", parameter_event.node.c_str());
for (const auto& p : parameter_event.changed_parameters) { RCLCPP_INFO( this->get_logger(), "Inside event: \"%s\" changed to %s", p.name.c_str(), rclcpp::Parameter::from_parameter_msg(p).value_to_string().c_str()); }; };event_cb_handle_ = param_subscriber_->add_parameter_event_callback(event_cb);这声明了一个新的 double 参数 another_double_param,并添加了一个事件回调来同时监控两个参数。请注意,parameter_event 的类型为 rcl_interfaces/msg/ParameterEvent。虽然本教程中没有展示,但事件回调还可以用于监控参数的添加或删除。
最后,不要忘记将事件回调句柄添加为 private 成员:
private: ... std::shared_ptr<rclcpp::ParameterEventCallbackHandle> event_cb_handle_;切换回工作空间的根目录 ros2_ws,像之前一样重新编译更新后的功能包:
$ colcon build --packages-select cpp_parameter_event_handler然后 source setup 文件:
Linux / macOS
$ . install/setup.bashWindows
$ call install\setup.bat要测试新的事件回调,首先运行 parameter_event_handler 节点:
$ ros2 run cpp_parameter_event_handler parameter_event_handler然后,从第二个终端(已 source ROS)设置原始的 int 参数:
$ ros2 param set node_with_parameters an_int_param 44执行该命令后,你应该看到单参数回调和事件回调都被触发了:
[INFO] [1747144403.418980063] [node_with_parameters]: cb: Received an update to parameter "an_int_param" of type integer: "44" [INFO] [1747144403.419086611] [node_with_parameters]: Received parameter event from node "/node_with_parameters" [INFO] [1747144403.419114103] [node_with_parameters]: Inside event: "an_int_param" changed to 44现在设置新的 double 参数:
$ ros2 param set node_with_parameters another_double_param 4.4由于没有为 double 参数添加单参数回调(通过 add_parameter_callback),我们应该只会看到事件回调触发:
[INFO] [1747144452.917437113] [node_with_parameters]: Received parameter event from node "/node_with_parameters" [INFO] [1747144452.917591649] [node_with_parameters]: Inside event: "another_double_param" changed to 4.400000注意:同时设置多个参数时,最好使用
set_parameters_atomically,详见关于参数的教程。这样事件回调只会触发一次。
你创建了一个带参数的节点,并使用 ParameterEventHandler 类设置回调来监控该参数的变化。你还使用同一个类来监控远程节点的变化,以及在单个事件回调中监控所有参数。ParameterEventHandler 是一种监控参数变化的便捷方式,让你能够对更新后的值做出响应。
要了解如何将 ROS 1 参数文件适配到 ROS 2,请参阅从 ROS 1 迁移参数的教程。