ROS2参数服务——在线调参的正确姿势
面试被问:"ROS2的参数系统是怎么工作的?你怎么在不重启节点的情况下修改参数?"
我说"用ros2 param set命令行"。面试官说:"那在代码里怎么声明参数?参数变了怎么通知节点?参数文件怎么用?"
一连串问题下来,我发现自己对参数系统的了解太浅了。今天把ROS2参数服务彻底讲清楚。
参数是什么
参数(Parameter)是附加在Node上的配置值。每个Node可以声明任意数量的参数,每个参数有名字、类型和值。
常见的参数比如:控制频率、PID参数、传感器阈值、通信超时时间。这些东西在开发阶段经常需要调整,如果每次改个数值都要重新编译,效率太低了。
ROS2的参数系统设计得比较灵活:可以在代码里声明默认值,可以在launch文件里覆盖,可以在命令行里修改,还可以从YAML文件批量加载。
声明和使用参数
在Node的构造函数里声明参数:
class ControllerNode(Node): def __init__(self): super().__init__('controller') # 声明参数,带默认值 self.declare_parameter('control_rate', 100.0) self.declare_parameter('kp', 1.5) self.declare_parameter('ki', 0.01) self.declare_parameter('kd', 0.2) self.declare_parameter('max_speed', 1.0) # 获取参数值 self.control_rate = self.get_parameter('control_rate').value self.kp = self.get_parameter('kp').value # 创建定时器 self.timer = self.create_timer( 1.0 / self.control_rate, self.control_loop)declare_parameter的第一个参数是名字,第二个是默认值。类型由默认值自动推断——100.0是float,True是bool,"hello"是string。
你也可以不传默认值,这样参数就没有初始值,必须在使用前通过外部方式设置。如果没设置就访问,会抛异常。
运行时修改参数
这是参数系统最强大的地方。不用重启节点,直接在线修改:
命令行方式:
ros2 param set /controller kp 2.0 ros2 param set /controller max_speed 0.5YAML文件方式:
/controller: ros__parameters: kp: 2.0 ki: 0.02 kd: 0.3 max_speed: 0.8加载命令:
ros2 param load /controller params.yaml修改之后,节点需要重新获取参数值才能生效。上面的命令行修改后,如果你还在用self.kp这个变量,它的值不会自动更新。你需要注册一个参数回调:
self.add_on_set_parameters_callback( self.parameters_callback) def parameters_callback(self, params): result = SetParametersResult() result.successful = True for param in params: if param.name == 'kp': self.kp = param.value self.get_logger().info(f'kp更新为: {self.kp}') elif param.name == 'max_speed': if param.value < 0 or param.value > 3.0: result.successful = False result.reason = '速度超出安全范围' return result self.max_speed = param.value return result这个回调函数有两个作用:一是把新值同步到成员变量,二是可以做参数校验。比如max_speed超过3.0就拒绝设置,返回失败。这在安全要求高的机器人项目中非常重要。
参数回调和校验
参数回调的校验能力经常被忽略,但它真的很实用。
举个例子:你的机器人有个参数是"最大线速度"。如果有人在运行时不小心设成了10m/s,机器人可能会失控。通过参数回调,你可以限制这个值的范围,超出就拒绝。
再比如,某些参数修改后需要重新计算派生值。PID参数改了之后,可能需要重新初始化积分项。这些逻辑都可以放在参数回调里。
def parameters_callback(self, params): for param in params: if param.name == 'pid': if len(param.value) != 3: return SetParametersResult( successful=False, reason='PID需要三个值') self.kp, self.ki, self.kd = param.value self.integral = 0.0 # 重置积分项 return SetParametersResult(successful=True)在Launch文件中使用参数
实际项目中,参数通常在launch文件里设置:
from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): return LaunchDescription([ Node( package='my_robot', executable='controller', name='controller', parameters=[{ 'control_rate': 100.0, 'kp': 1.5, 'ki': 0.01, 'kd': 0.2, 'max_speed': 1.0 }] ) ])也可以从YAML文件加载:
Node( package='my_robot', executable='controller', parameters=[os.path.join( get_package_share_directory('my_robot'), 'config', 'controller.yaml')] )这种方式的好处是参数和代码分离。不同场景用不同的参数文件,不用改代码就能切换配置。
查看和导出参数
ros2 param list /controller——列出节点的所有参数。
ros2 param get /controller kp——获取某个参数的当前值。
ros2 param dump /controller——导出当前所有参数值,格式是YAML。这个命令在调试时特别有用:你把参数调到满意了,dump下来保存成文件,以后就能复现这个配置。
面试中怎么聊
面试官问参数,你可以说:"ROS2的参数系统支持声明默认值、运行时修改、参数校验和YAML文件加载。通过declare_parameter声明参数,add_on_set_parameters_callback注册回调来同步值和做校验。Launch文件可以覆盖默认参数,支持字典和YAML文件两种方式。ros2 param dump可以导出当前配置,方便复现和版本管理。"
参数管理的工程实践
实际项目中参数管理比想象中复杂。当系统有几十个节点、每个节点有十几个参数时,手动管理就不现实了。解决方案是用YAML文件集中管理参数,在launch文件中加载。ROS2还提供了参数事件通知机制,可以监听参数变化并做出响应。一个实用的技巧是把参数分为调试参数和生产参数,分别维护不同的配置文件。
给你的建议
把你项目里的硬编码数值(超时时间、阈值、增益系数)全部改成参数。第一次改可能觉得麻烦,但等你需要在不重启的情况下调参数的时候,你会感谢自己。
参数文件放到config/目录下,按功能模块分文件:controller.yaml、sensor.yaml、navigation.yaml。在launch文件里加载对应的配置文件。这套结构一旦搭好,后续调参效率会高很多。
上一篇:第116篇 ROS2 Action通信——长时任务与三阶段反馈机制
下一篇预告:第118篇 ROS2动态重配置——运行时改变系统行为