4、参数动态设置:set_parameters()方法、参数回调函数on_set_parameters_callback、参数变更事件
参数动态设置,说白了就是让节点在运行时能改配置。
不用重启节点,不用重新编译,改个参数值,节点行为就变了。
我刚开始用ROS2时,总觉得参数就是启动时读一次就完事了。后来做一个机器人底盘驱动,发现PID参数必须在线调,不然每次改参数都要重启节点,调试效率极低。嗯,从那以后我就把参数动态设置玩得透透的了。
4.1 核心方法:set_parameters()
这是最直接的参数修改方式。节点内部或外部都可以调用它。
// C++ 示例
auto param = rclcpp::Parameter("speed", 0.5);
node->set_parameters({param});
// Python 示例
param = rclpy.parameter.Parameter("speed", rclpy.Parameter.Type.DOUBLE, 0.5)
node.set_parameters([param])
注意,set_parameters() 返回的是一个 SetParametersResult 列表。每个结果告诉你对应的参数是否设置成功。
关键点:set_parameters() 是异步的。它不会阻塞等待参数生效,而是立即返回。如果你需要确认参数是否真的被应用了,检查返回值。
4.2 参数回调函数:on_set_parameters_callback
这是参数动态设置的灵魂。没有回调,参数改了你也感知不到。
回调函数会在参数被修改时触发。你可以在这里做三件事:
- 验证参数:检查新值是否合法
- 响应参数:根据新值调整节点行为
- 拒绝参数:如果值不合法,返回失败
// C++ 回调注册
auto callback = [this](const std::vector<rclcpp::Parameter>& params) -> rcl_interfaces::msg::SetParametersResult {
rcl_interfaces::msg::SetParametersResult result;
result.successful = true;
for (const auto& param : params) {
if (param.get_name() == "speed" && param.as_double() < 0.0) {
RCLCPP_WARN(this->get_logger(), "速度不能为负,已拒绝");
result.successful = false;
result.reason = "speed must be non-negative";
return result;
}
}
return result;
};
node->set_on_parameters_set_callback(callback);
# Python 回调注册
def param_callback(params):
result = rclpy.parameter.SetParametersResult()
result.successful = True
for param in params:
if param.name == "speed" and param.value < 0.0:
node.get_logger().warn("速度不能为负,已拒绝")
result.successful = False
result.reason = "speed must be non-negative"
return result
return result
node.add_on_set_parameters_callback(param_callback)
我的习惯:回调函数里尽量只做验证和轻量级响应。别在回调里做耗时操作,比如写文件、网络请求。否则会影响参数设置的响应速度。
4.3 参数变更事件
除了回调,ROS2还提供了参数变更事件。这玩意儿比回调更底层,适合做日志记录或监控。
| 事件类型 | 触发时机 | 典型用途 |
|---|---|---|
| PARAMETER_EVENT_INITIALIZED | 参数首次声明时 | 记录初始配置 |
| PARAMETER_EVENT_CHANGED | 参数值被修改时 | 触发业务逻辑变更 |
| PARAMETER_EVENT_DELETED | 参数被删除时 | 清理资源 |
事件订阅方式如下:
// C++ 事件订阅
auto sub = node->get_node_parameters_interface()
->get_parameter_event_publisher()->on_parameter_event(
[](const rcl_interfaces::msg::ParameterEvent& event) {
for (const auto& changed : event.changed_parameters) {
RCLCPP_INFO(rclcpp::get_logger("event"),
"参数 %s 已变更", changed.name.c_str());
}
});
我曾经踩过的坑:参数事件是广播式的。如果你有多个节点订阅同一个参数事件,每个节点都会收到通知。别在事件处理里做重复性操作,否则可能引发连锁反应。
4.4 实战:在线调参流程
我做一个机器人导航模块时,需要在线调整最大速度。流程是这样的:
- 声明参数
max_speed,默认值 1.0 - 注册回调,验证速度不能超过 2.0
- 外部工具(如 rqt)修改参数
- 回调触发,更新内部速度限制
- 导航算法立即使用新速度
你看,整个过程不需要重启节点,不需要改代码。这就是动态设置的威力。
核心原则:参数动态设置不是让你随便改的。每个参数都应该有明确的取值范围和变更影响范围。否则,你可能会在运行时把系统搞崩。
4.5 知识体系图
下面这张图展示了参数动态设置的完整链路:
4.6 避坑指南
- 回调里别抛异常:参数回调如果抛出异常,ROS2会直接崩溃。我见过有人把文件读写放回调里,结果磁盘满了,节点直接挂掉。
- 参数名大小写敏感:ROS2的参数名是大小写敏感的。写代码时统一风格,我习惯全小写加下划线。
- 批量设置时注意原子性:set_parameters() 可以一次传多个参数。但注意,如果其中一个验证失败,已经成功的参数不会回滚。你需要自己处理一致性。
我的建议:刚开始用动态参数时,先在回调里加日志。每次参数变更都打印出来,这样你能清楚看到参数的变化轨迹。等系统稳定了再移除日志。
参数动态设置,说白了就是给节点装了个遥控器。你可以在运行时随时调整,不用停机。但遥控器不能乱按,每个参数变更都要经过回调的验证。这就是ROS2参数系统的设计哲学:灵活,但可控。