4、参数声明与访问:使用 declare_parameter 和 get_parameter 进行参数声明与读取

核心原理:参数服务器的“先声明,后访问”机制

在 ROS2 中,参数系统采用了一种类似“键值对注册表”的架构。每个节点都拥有一个独立的参数空间,其他节点无法直接修改该节点的参数,只能通过服务接口发起请求。这种设计保证了参数访问的线程安全与节点自治性。

与 ROS1 中参数服务器全局共享、任意读写不同,ROS2 要求节点在使用参数前必须显式声明。声明参数的本质是在节点内部建立一个参数描述符(ParameterDescriptor),它记录了参数名、类型、默认值、只读属性、动态范围等元信息。只有完成声明后,该参数才会出现在节点的参数列表中,才能被 get_parameter() 正常读取,也才能被外部节点通过 ros2 param set 命令修改。

这一机制的核心价值在于:类型安全(声明时锁定类型,避免运行时类型混乱)、默认值兜底(未从启动文件或命令行传入时,自动使用声明时的默认值)、可发现性(通过 ros2 param list 即可查看节点支持的全部参数)。

关键区别:ROS1 中 param.get() 即使参数不存在也只会返回空值;ROS2 中若未声明就调用 get_parameter(),会抛出 ParameterNotDeclaredException 异常。因此,声明是访问的前提,这是 ROS2 参数系统最重要的设计转变。
ROS2 参数声明与访问流程 ① 声明阶段 declare_parameter( "max_speed", 1.5, descriptor) ParameterDescriptor: type: DOUBLE read_only: False 节点参数表 max_speed → 1.5 robot_name → "tbot" ② 访问阶段 get_parameter( "max_speed") → Parameter 对象 Parameter 对象包含: .get_value() → 1.5 .get_type() → DOUBLE .as_double() → 1.5 类型安全转换 as_int() / as_string() as_bool() / as_double() ③ 外部交互 ros2 param set /node max_speed 2.0 → 触发参数回调 ros2 run pkg node --ros-args -p max_speed:=2.0 → 启动时覆盖默认值 on_set_parameters_callback: 校验新值范围 拒绝非法修改 返回 SetParametersResult 声明后 才能访问 参数值 同步

方法流程:从声明到读取的完整代码范式

在 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

误区与复盘:参数访问的常见陷阱

误区一:忘记声明直接读取。许多从 ROS1 迁移过来的开发者习惯直接调用 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 配置的加载顺序。参数系统的调试本质上是对“声明-加载-访问-回调”四环节的逐一排查。