3. 参数获取与读取:get_parameter()、get_parameters() 与类型检查

参数设好了,节点也跑起来了。但说实话,真正干活的时候,我们大部分时间都在「读参数」。你想想看,机器人启动后,PID 系数、速度限制、传感器阈值——这些东西都得从参数服务器拉下来,节点才能按你的意图跑。

ROS2 里读参数的方式很灵活。可以一个一个读,也可以一把全拉下来。我个人习惯是:初始化阶段批量获取,运行阶段按需读取。这样既省事,又不会在关键循环里卡 I/O。

3.1 get_parameter():单个参数读取

这是最基础的方法。你给它一个参数名,它返回一个 rclcpp::Parameter 对象。然后你得调用 .get_value<T>() 把值取出来。

// C++ 示例
auto param = this->get_parameter("max_linear_speed");
double max_speed = param.get_value<double>();

RCLCPP_INFO(this->get_logger(), "最大线速度: %.2f", max_speed);

Python 里更直接,get_parameter() 返回一个命名元组,你可以直接用 .value 拿到值:

# Python 示例
max_speed = self.get_parameter("max_linear_speed").value
self.get_logger().info(f"最大线速度: {max_speed}")
我的习惯: 如果参数是整数或浮点数,我建议在声明参数时就给默认值。这样即使参数服务器没配好,节点也能用兜底值跑起来,不至于直接崩溃。

3.2 get_parameters():批量获取

一个节点可能有几十个参数。你一个个 get_parameter() 去读,代码会变得又臭又长。这时候就该 get_parameters() 上场了。

它接受一个 vector<string> 参数名列表,返回一个 vector<Parameter>。我一般在节点的构造函数里用这个方法,一次性把所有参数拉下来:

// C++ 批量获取
std::vector<std::string> param_names = {
    "max_linear_speed",
    "max_angular_speed",
    "acceleration",
    "deceleration"
};

auto params = this->get_parameters(param_names);

double max_linear  = params[0].get_value<double>();
double max_angular = params[1].get_value<double>();
double accel       = params[2].get_value<double>();
double decel       = params[3].get_value<double>();

Python 版本更简洁:

# Python 批量获取
param_names = [
    "max_linear_speed",
    "max_angular_speed",
    "acceleration",
    "deceleration"
]

params = self.get_parameters(param_names)
max_linear  = params[0].value
max_angular = params[1].value
accel       = params[2].value
decel       = params[3].value
注意顺序: 返回的参数列表顺序,和你传入的参数名列表顺序完全一致。所以你可以放心地用索引取值。但千万别搞混了顺序——我曾经在项目里因为调换了两个参数的位置,导致机器人加速和减速搞反了,差点撞墙。

3.3 参数类型检查

嗯,这里要注意。ROS2 的参数是有类型的。你声明了一个 double 类型的参数,结果 YAML 文件里写成了字符串 "1.5",或者启动时传了个整数 1——节点不会自动帮你转换。

所以,拿到参数后,我建议一定要做类型检查。尤其是当你的代码要给别人用时,你永远不知道对方会在启动文件里塞什么奇怪的东西。

每个 Parameter 对象都有一个 .get_type() 方法,返回一个枚举值:

类型枚举 对应值 说明
rclcpp::ParameterType::PARAMETER_NOT_SET 0 参数未设置
PARAMETER_BOOL 1 布尔值
PARAMETER_INTEGER 2 64 位整数
PARAMETER_DOUBLE 3 双精度浮点数
PARAMETER_STRING 4 字符串
PARAMETER_BYTE_ARRAY 5 字节数组
PARAMETER_BOOL_ARRAY 6 布尔数组
PARAMETER_INTEGER_ARRAY 7 整数数组
PARAMETER_DOUBLE_ARRAY 8 浮点数数组
PARAMETER_STRING_ARRAY 9 字符串数组

实战中,我一般这样写类型检查:

// C++ 类型检查示例
auto param = this->get_parameter("max_linear_speed");

if (param.get_type() != rclcpp::ParameterType::PARAMETER_DOUBLE) {
    RCLCPP_ERROR(this->get_logger(),
        "参数 max_linear_speed 类型错误,期望 double,实际为 %s",
        param.get_type_name().c_str());
    return;
}

double speed = param.get_value<double>();

Python 里用 .type_ 属性判断:

# Python 类型检查示例
from rclpy.parameter import Parameter

param = self.get_parameter("max_linear_speed")

if param.type_ != Parameter.Type.DOUBLE:
    self.get_logger().error(
        f"参数 max_linear_speed 类型错误,期望 double,实际为 {param.type_}"
    )
    return

speed = param.value
避坑指南: 我曾经在调试一个导航堆栈时,发现机器人转弯特别迟钝。查了半天,发现是 max_angular_speed 在 YAML 里写成了 "2.0"(带引号),被解析成了字符串。ROS2 不会报错,但 get_value<double>() 会抛异常。从那以后,我所有参数读取后面都跟了类型检查。

3.4 参数读取的完整流程

把上面这些串起来,一个健壮的参数读取流程应该是这样的:

  1. 声明参数:用 declare_parameter() 给每个参数一个默认值。
  2. 批量获取:在构造函数里用 get_parameters() 一次性拉下来。
  3. 类型检查:对每个参数检查类型是否匹配。
  4. 赋值使用:把值赋给成员变量,后续直接用。

下面这张图把这个流程串起来了:

参数读取完整流程 声明参数 declare_parameter() 批量获取 get_parameters() 类型检查 get_type() 判断 赋值使用 成员变量存储 类型不匹配 报错 + 使用默认值 核心原则 先声明 → 再读取 → 后检查 绝不信任外部输入

说白了,参数读取这件事,核心就一句话:先声明,再读取,后检查。声明保证参数存在,读取拿到值,检查确保类型正确。三步走完,你的节点才能稳稳当当地跑起来。

我的建议: 把参数读取和类型检查封装成一个工具函数。比如 getDoubleParam()、getIntParam(),内部自动做类型检查,类型不对就打印警告并返回默认值。这样你的业务代码会干净很多。