想象你有一辆车,每次出发前都要设置油箱盖拧紧力矩、大灯高度、雨刮速度。你不会希望每次改这些设置都得重写整车程序——只要拧几个旋钮就行。ROS2的参数服务器就是这些"旋钮"的软件版:它让节点可以在不修改代码、甚至不重启节点的情况下,调整运行时行为。
参数服务器本质上是一个由中心节点(通常是ros2 daemon)管理的键值对数据库。每个节点可以拥有自己的一组参数,其他节点也能通过服务接口查询或修改。参数值支持整数、浮点、布尔、字符串等各种基本类型。
| 维度 | 参数 | 话题 (Topic) | 服务 (Service) |
|---|---|---|---|
| 数据流向 | 单值读写 | 持续流式 | 请求-应答 |
| 典型用途 | 配置项 | 传感器数据、状态 | 触发操作、查询 |
| 调用方式 | get/set | publish/subscribe | call/response |
| 是否需要命名 | 节点命名空间内唯一 | 全局唯一 | 全局唯一 |
| 是否持久化 | 可保存到YAML文件 | 不保存 | 不保存 |
参数适合放那些"改一次管很久"的值——比如PID控制器的三个系数、激光雷达的扫描频率、机器人的轮距。如果你发现自己每秒都在读一个参数,那它就应该是个话题。
给节点增加参数只需三步:声明、获取、设置。看一个具体例子:
// 给一个控制节点添加两个参数
class MotorController : public rclcpp::Node {
public:
MotorController() : Node("motor_controller") {
// 1. 声明参数,同时指定默认值
this->declare_parameter<double>("max_current", 2.5);
this->declare_parameter<std::string>("model", "MG995");
// 2. 读取参数值
double current_limit;
this->get_parameter("max_current", current_limit);
// 3. 也可以随时修改
this->set_parameter(rclcpp::Parameter("max_current", 3.0));
// 4. 注册回调,当参数被外部修改时触发
param_cb_ = this->add_on_set_parameters_callback(
std::bind(&MotorController::on_param_change, this, std::placeholders::_1));
}
private:
rcl_interfaces::msg::SetParametersResult
on_param_change(const std::vector<rclcpp::Parameter>& params) {
// 可以校验新值,比如不允许电流超过上限
for (auto& p : params) {
if (p.get_name() == "max_current" && p.as_double() > 5.0) {
return rcl_interfaces::msg::SetParametersResult();
}
}
return rcl_interfaces::msg::SetParametersResult();
}
rclcpp::AsyncParametersClient::SharedPtr param_client_;
rclcpp::node_interfaces::OnSetParametersCallbackHandle::SharedPtr param_cb_;
};
一个参数从声明开始存在,在节点析构时自动销毁。节点启动时可以从YAML文件批量加载参数——这是生产环境中设置参数的标准做法。参数值支持修改后动态更新,但除非显式保存,否则不会回写到YAML。
ROS2参数支持七种基本类型,以及对应的数组变体。所有值通过统一的ParameterValue结构体传递:
| 类型 | C++对应类型 | 数组版本 |
|---|---|---|
| bool | bool | bool数组 |
| integer | int64_t | int64数组 |
| double | double | double数组 |
| string | std::string | string数组 |
| byte | uint8_t | byte数组 |
数组类型的get/set接口与标量一致,只是模板参数换成std::vectorgains.p、gains.i、gains.d。
启动节点时,你可以指定一个YAML文件来预填参数。这是一种标准做法,避免了在代码里硬编码。YAML格式固定为:
# motor_params.yaml
motor_controller: # 必须与节点名完全一致
ros__parameters:
max_current: 3.5
model: "MG996R"
enable_debug: false
gains:
p: 1.2
i: 0.5
d: 0.01
启动时用ros2 run my_pkg motor_node --ros-args --params-file motor_params.yaml就能加载。注意节点名必须匹配,否则参数不会被注入——我在现场调试时因为这个原因花了一个小时排查。
gains.p)是ROS2接受的写法,底层自动展平。如果你在YAML里看到带-的列表,对应的是数组类型参数。
我在项目中遇到过最头疼的问题:参数回调用线程不安全的操作——比如在回调里直接访问共享的电机对象却忘了加锁,导致随机死机。解决办法是在回调里只校验和存储新值,真正的应用修改放到主循环中检查。
printer_node的节点,声明两个参数:prefix(字符串,默认"Hello")和repeat(整数,默认1)。prefix + " World!"共repeat次。ros2 param set /printer_node prefix "Hi"修改前缀,观察打印内容变化。prefix为"Hey"并重新启动节点,验证参数被加载。speed的参数和叫speed的话题。(对/错)