4、参数声明与访问:使用 declare_parameter 和 get_parameter 进行参数声明与读取
核心原理:参数服务器的“先声明,后访问”机制
在 ROS2 中,参数系统采用了一种类似“键值对注册表”的架构。每个节点都拥有一个独立的参数空间,其他节点无法直接修改该节点的参数,只能通过服务接口发起请求。这种设计保证了参数访问的线程安全与节点自治性。
与 ROS1 中参数服务器全局共享、任意读写不同,ROS2 要求节点在使用参数前必须显式声明。声明参数的本质是在节点内部建立一个参数描述符(ParameterDescriptor),它记录了参数名、类型、默认值、只读属性、动态范围等元信息。只有完成声明后,该参数才会出现在节点的参数列表中,才能被 get_parameter() 正常读取,也才能被外部节点通过 ros2 param set 命令修改。
这一机制的核心价值在于:类型安全(声明时锁定类型,避免运行时类型混乱)、默认值兜底(未从启动文件或命令行传入时,自动使用声明时的默认值)、可发现性(通过 ros2 param list 即可查看节点支持的全部参数)。
param.get() 即使参数不存在也只会返回空值;ROS2 中若未声明就调用 get_parameter(),会抛出 ParameterNotDeclaredException 异常。因此,声明是访问的前提,这是 ROS2 参数系统最重要的设计转变。
方法流程:从声明到读取的完整代码范式
在 C++ 节点中,参数声明通常放在构造函数中,确保节点启动时参数表已就绪。以下是一个标准流程,涵盖声明、读取、类型转换与异常处理四个环节。
// 1. 包含头文件
#include "rclcpp/rclcpp.hpp"
class RobotController : public rclcpp::Node {
public:
RobotController() : Node("robot_controller") {
// 2. 声明参数(带描述符)
auto descriptor = rcl_interfaces::msg::ParameterDescriptor{};
descriptor.description = "最大线速度 (m/s)";
descriptor.floating_point_range = {{
rcl_interfaces::msg::FloatingPointRange{}
.set__from_value(0.0)
.set__to_value(5.0)
}};
this->declare_parameter("max_speed", 1.5, descriptor);
this->declare_parameter("robot_name", "tbot");
this->declare_parameter("enable_debug", false);
// 3. 读取参数(推荐使用模板版本)
double max_speed = this->get_parameter("max_speed").as_double();
std::string robot_name = this->get_parameter("robot_name").as_string();
bool debug = this->get_parameter("enable_debug").as_bool();
RCLCPP_INFO(this->get_logger(),
"机器人: %s, 最大速度: %.2f m/s, 调试: %s",
robot_name.c_str(), max_speed, debug ? "开" : "关");
}
};
对于 Python 节点,语法更为简洁,但流程完全一致:
import rclpy
from rclpy.node import Node
from rcl_interfaces.msg import ParameterDescriptor
class RobotController(Node):
def __init__(self):
super().__init__('robot_controller')
# 声明参数
self.declare_parameter('max_speed', 1.5)
self.declare_parameter('robot_name', 'tbot')
# 读取参数(返回 Parameter 对象)
max_speed = self.get_parameter('max_speed').value
robot_name = self.get_parameter('robot_name').value
self.get_logger().info(f'机器人: {robot_name}, 速度: {max_speed}')
declare_parameter 的返回值直接初始化成员变量,避免后续重复调用 get_parameter()。例如:double max_speed_ = this->declare_parameter("max_speed", 1.5).as_double(); 这样既完成了声明,又完成了首次读取。
案例场景:机器人底盘参数动态配置
假设我们正在开发一个差速驱动机器人底盘节点,需要支持运行时调整最大速度、加速度和 PID 参数。通过参数系统,我们可以实现“启动时加载配置,运行时动态调整”的完整闭环。
| 参数名 | 类型 | 默认值 | 描述 | 动态可调 |
|---|---|---|---|---|
max_speed |
double | 1.5 | 最大线速度 (m/s) | 是 |
max_accel |
double | 0.8 | 最大加速度 (m/s²) | 是 |
wheel_base |
double | 0.35 | 轮距 (m) | 否(只读) |
pid_kp |
double | 1.2 | PID 比例系数 | 是 |
enable_safety |
bool | true | 启用防碰撞 | 是 |
在节点中,我们不仅需要读取参数,还需要监听参数修改事件,以便在运行时安全地应用新值。这需要注册一个参数回调函数:
rcl_interfaces::msg::SetParametersResult
RobotController::param_callback(
const std::vector<rclcpp::Parameter>& params) {
for (const auto& p : params) {
if (p.get_name() == "max_speed") {
// 校验新值是否在安全范围内
if (p.as_double() > 5.0) {
RCLCPP_WARN(this->get_logger(),
"max_speed 超限,拒绝修改");
return rcl_interfaces::msg::SetParametersResult{
.successful = false,
.reason = "速度超过安全阈值"};
}
}
}
return rcl_interfaces::msg::SetParametersResult{.successful = true};
}
启动时通过 YAML 文件加载参数,实现“一处配置,多处复用”:
# robot_params.yaml
robot_controller:
ros__parameters:
max_speed: 2.0
max_accel: 1.0
wheel_base: 0.35
pid_kp: 1.5
enable_safety: true
# 启动命令
ros2 run my_robot robot_controller_node \
--ros-args --params-file robot_params.yaml
误区与复盘:参数访问的常见陷阱
get_parameter(),在 ROS2 中这会抛出 ParameterNotDeclaredException。正确做法是:要么先调用 declare_parameter(),要么使用 get_parameter_or() 方法提供默认值兜底。
int 的参数,如果启动文件传入 1.5,会触发类型转换异常。ROS2 参数类型是强约束的,declare_parameter("count", 0) 后,ros2 param set /node count 1.5 会被拒绝。建议在声明时明确使用 ParameterType 枚举,并在回调中做类型检查。
on_set_parameters_callback 返回 successful = false,外部修改请求会被拒绝,但节点内部不会收到任何通知。务必在回调中记录日志,方便排查“为什么参数没变”的问题。
复盘建议:在开发调试阶段,养成使用 ros2 param list 和 ros2 param get 命令检查节点参数表的习惯。若发现参数缺失,优先检查是否已声明;若参数值异常,检查启动文件与 YAML 配置的加载顺序。参数系统的调试本质上是对“声明-加载-访问-回调”四环节的逐一排查。