地面移动底盘速度指令安全监督
问题
移动底盘的 /cmd_vel 通常直接来自遥控、导航栈或算法节点。一旦上游发出超限速度、卡死不再更新、发出 NaN/Inf 这类非法值,或者需要紧急停车,底盘会继续按最后一条指令运动,容易撞墙、冲出场地。安全做法是在 /cmd_vel 之前插入一个监督节点,对所有速度指令做统一处理。
本模块实现四道保护:
- 限幅:线速度与角速度超过参数上限时截断,防止底盘超速;
- 急停:
/emergency_stop为true时立即输出零速度并清除缓存的旧指令,解除后必须收到新指令才恢复输出; - 超时保护:超过
cmd_timeout秒没有收到新指令时自动停车,防止上游崩溃后底盘继续运动; - 非法值拦截:指令含
NaN或Inf时直接输出零速度,避免非法值被当成上限速度。
参数本身也会校验:速度上限必须为非负有限值,cmd_timeout 与 publish_rate 必须大于 0,否则节点打印错误日志后退出。
环境要求
- ROS 2 Humble
- 只用
rclpy、geometry_msgs、std_msgs,不需要 Gazebo 或任何仿真器
文件结构
src/ground/cmd_vel_guard/
├── resource/cmd_vel_guard
├── cmd_vel_guard/
│ ├── __init__.py
│ └── cmd_vel_guard_node.py # 监督节点
├── launch/
│ └── main.launch.py
├── test/
│ └── test_cmd_vel_guard.py # 单元测试
├── package.xml
├── setup.cfg
├── setup.py
└── README.md
话题与参数
| 方向 | 话题 | 类型 |
|---|---|---|
| 订阅 | /cmd_vel_raw |
geometry_msgs/Twist |
| 订阅 | /emergency_stop |
std_msgs/Bool |
| 发布 | /cmd_vel |
geometry_msgs/Twist |
| 发布 | /guard_status |
std_msgs/String |
| 参数 | 默认值 | 说明 |
|---|---|---|
max_linear_x |
0.3 | 前进/后退速度上限,m/s,须为非负有限值 |
max_linear_y |
0.0 | 横向速度上限,差速底盘为 0 |
max_angular_z |
0.8 | 角速度上限,rad/s,须为非负有限值 |
cmd_timeout |
2.0 | 指令超时时间,秒,须大于 0 |
publish_rate |
20.0 | 输出频率,Hz,须大于 0 |
/guard_status 取值:waiting(尚未收到指令)、ok、clamped(已截断)、timeout、estop、invalid(收到 NaN/Inf)。
输出的 /cmd_vel 只包含 linear.x、linear.y、angular.z 三个分量,linear.z、angular.x、angular.y 恒为 0,适配差速与阿克曼底盘;上游若有其他分量需求,需要另行扩展。
编译
cd ~/ros2_ws
colcon build --packages-select cmd_vel_guard
source install/setup.bash
运行
ros2 launch cmd_vel_guard main.launch.py
launch 暴露全部 5 个参数,例如把速度上限改成 0.2 m/s、超时改成 1 秒、输出改成 10 Hz:
ros2 launch cmd_vel_guard main.launch.py \
max_linear_x:=0.2 cmd_timeout:=1.0 publish_rate:=10.0
演示步骤
打开三个终端,都先执行 source ~/ros2_ws/install/setup.bash。
终端 1:启动监督节点
ros2 launch cmd_vel_guard main.launch.py
终端 2:以 10 Hz 发布超速指令(0.8 m/s,上限 0.3)
ros2 topic pub -r 10 /cmd_vel_raw geometry_msgs/msg/Twist "{linear: {x: 0.8}}"
终端 3:查看经过保护后的指令
ros2 topic echo /cmd_vel
预期结果:
- 终端 1 打印
指令超限已截断: x 0.80->0.30, z 0.00->0.00(1 秒最多一次,避免刷屏) - 终端 3 中
linear.x稳定为0.3,/guard_status为clamped
急停演示(终端 4):
ros2 topic pub --once /emergency_stop std_msgs/msg/Bool "{data: true}"
ros2 topic echo /cmd_vel # 应持续为全零,/guard_status 为 estop
解除急停后不会立刻恢复运动:急停触发时节点已清除缓存的旧指令,急停期间收到的指令也被丢弃,所以解除后 /guard_status 变为 waiting 并保持零速,必须重新发送 /cmd_vel_raw 才恢复输出,避免底盘接着执行急停前的旧速度。
超时演示:在终端 2 按 Ctrl+C 停止发布,超过 cmd_timeout 后终端 1 打印 2.1 秒未收到速度指令,已自动停车(秒数为实际耗时,保留一位小数),/cmd_vel 变为全零,/guard_status 为 timeout。
非法值演示:
ros2 topic pub --once /cmd_vel_raw geometry_msgs/msg/Twist "{linear: {x: NaN}}"
终端 1 打印 收到非法速度指令(NaN/Inf),已输出零速度,/cmd_vel 为全零,/guard_status 为 invalid。
测试
colcon test --packages-select cmd_vel_guard
colcon test-result --verbose
覆盖内容:_clamp 的正常值、边界值与 NaN/Inf,急停优先于超时,超时输出全零,非法指令输出全零,正常与超速指令的输出,急停清除缓存后「解除但不再发送新指令」保持零速,急停期间收到的指令被丢弃,以及非法参数被拒绝。
验收标准
| 检查项 | 预期 |
|---|---|
| 正常指令 | 输入 0.2 m/s,输出 0.2 m/s,/guard_status 为 ok |
| 限幅 | 输入 0.8 m/s,输出 0.3 m/s,状态 clamped |
| 急停 | /emergency_stop 为 true 时输出全零,日志提示急停 |
| 急停解除 | 解除后不发送新指令则保持全零,状态 waiting |
| 超时 | 停止发布超过 cmd_timeout 后输出全零,状态 timeout |
| 非法值 | 输入含 NaN/Inf 时输出全零,状态 invalid |
| 参数校验 | 上限为负、cmd_timeout 或 publish_rate 不为正时节点报错退出 |
| 频率 | /cmd_vel 稳定在 20 Hz |
| 单元测试 | colcon test 全部通过 |
| 依赖 | 除 ROS 2 Humble 外无额外依赖,不需要仿真器 |
扩展方向
- 把
/guard_status换成diagnostic_msgs/DiagnosticArray,接入rqt_robot_monitor; - 增加
cmd_vel平滑滤波(一阶低通),减少速度突变; - 加入地理位置限速区:订阅
/odom或定位话题,在特定区域内自动降速。