Ranger Mini V3 底盘运动性能测试与录包步骤

博客地址:https://www.cnblogs.com/zylyehuo/
适用环境:Ubuntu 22.04、ROS 2 Humble、Ranger Mini V3。
录制话题固定为:/cmd_vel/odom/lio/odom/tf/tf_static
全部八部分累计应有 157 个 bag

1. 测试目标

本测试仅负责采集 Ranger Mini V3 的底盘运动数据。所有 rosbag 录制完成后,再统一进行数据处理、轨迹对齐和误差统计。

测试分为以下五部分:

  1. 静止误差测试;
  2. 正向与反向直线运动测试;
  3. 正向与反向圆周运动测试;
  4. 正向与反向启停响应测试;
  5. 正向与反向转向切换及回正测试。

2. 重要操作原则

  • 每一次测试对应一个独立 bag,不把多个工况录入同一个 bag。
  • 每一个测试小节中,终端 1 的录包指令只对应紧随其后的终端 2 速度指令。
  • 每次录包开始后先静止 5 秒,再发布运动指令。
  • 运动结束后必须持续发布零速度,保留完整停车过程。
  • 零速度发布结束、车辆确认停止后,再在录包终端按 Ctrl+C
  • 每次动态测试都尽量从相同起点和相同初始朝向开始。
  • 测试过程中不得同时运行导航、LQR、MPPI、键盘遥控或其他 /cmd_vel 发布节点。

3. 终端分工

终端 作用
终端 1 执行当前测试对应的 ros2 bag record 指令
终端 2 执行当前测试对应的 /cmd_vel 指令
终端 3 保留紧急停车指令

4. 一次性建立测试目录

bash 复制代码
mkdir -p ~/ranger_motion_test/01_static_error
mkdir -p ~/ranger_motion_test/02_straight/forward
mkdir -p ~/ranger_motion_test/02_straight/reverse
mkdir -p ~/ranger_motion_test/03_circle/forward
mkdir -p ~/ranger_motion_test/03_circle/reverse
mkdir -p ~/ranger_motion_test/04_start_stop/forward
mkdir -p ~/ranger_motion_test/04_start_stop/reverse
mkdir -p ~/ranger_motion_test/05_steering_switch/forward
mkdir -p ~/ranger_motion_test/05_steering_switch/reverse

检查目录:

bash 复制代码
find ~/ranger_motion_test -type d | sort

目录结构:

text 复制代码
~/ranger_motion_test/
├── 01_static_error/
├── 02_straight/
│   ├── forward/
│   └── reverse/
├── 03_circle/
│   ├── forward/
│   └── reverse/
├── 04_start_stop/
│   ├── forward/
│   └── reverse/
└── 05_steering_switch/
    ├── forward/
    └── reverse/

5. 测试前检查

5.1 检查 /cmd_vel 消息类型

bash 复制代码
ros2 topic type /cmd_vel

预期输出:

text 复制代码
geometry_msgs/msg/Twist

5.2 检查 /cmd_vel 发布者

bash 复制代码
ros2 topic info /cmd_vel -v

正式测试前,应关闭导航、LQR、MPPI、遥控器和其他所有 /cmd_vel 发布节点。

5.3 检查轮式里程计

bash 复制代码
ros2 topic hz /odom
bash 复制代码
ros2 topic echo /odom --once

5.4 检查雷达定位里程计

bash 复制代码
ros2 topic hz /lio/odom
bash 复制代码
ros2 topic echo /lio/odom --once

5.5 紧急停车指令

终端 3 中提前保留以下命令。车辆运动异常时立即执行:

bash 复制代码
timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

6. 测试总表

部分 测试内容 工况数 单工况次数 Bag 数量
第一部分 静止误差 1 3 3
第二部分 正反向直线 6 3 18
第三部分 正反向圆周 4 3 12
第四部分 正反向启停 2 5 10
第五部分 正反向转向切换 2 3 6
总计 --- 15 --- 49

第一部分:静止误差测试

1.1 作用

用于记录车辆完全静止时,轮式里程计和雷达定位里程计的位置漂移、航向漂移、随机波动和定位跳变。该结果决定后续厘米级运动误差统计的可信下限。

1.2 测试要求

  • 车辆保持上电且完全静止;
  • 不触碰车辆;
  • 人员不要在雷达近距离范围内频繁移动;
  • 每次持续 60 秒;
  • 共录制 3 次。

静止测试第 1 次:STATIC_T60_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/01_static_error/STATIC_T60_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

持续发布零速度 60 秒:

bash 复制代码
timeout --signal=INT 60s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

静止测试第 2 次:STATIC_T60_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/01_static_error/STATIC_T60_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

持续发布零速度 60 秒:

bash 复制代码
timeout --signal=INT 60s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

静止测试第 3 次:STATIC_T60_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/01_static_error/STATIC_T60_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

持续发布零速度 60 秒:

bash 复制代码
timeout --signal=INT 60s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第二部分:正向与反向直线运动测试

2.1 作用

用于评价实际速度、实际行驶距离、直线横向漂移、航向漂移、正反向对称性,以及轮式里程计相对于雷达定位里程计的累计偏差。

2.2 场地要求

  • 建议在地面标记一条至少 7 m 的直线;
  • 标记车辆初始参考点和车身初始中心线;
  • 前方和后方均预留足够安全距离;
  • 每次测试重新回到相同起点和相同朝向。

2.3 工况表

工况 linear.x angular.z 运动时间 理论距离 次数
正向直线 0.2 m/s 0 rad/s 10 s 2.0 m 3
反向直线 -0.2 m/s 0 rad/s 10 s -2.0 m 3
正向直线 0.4 m/s 0 rad/s 10 s 4.0 m 3
反向直线 -0.4 m/s 0 rad/s 10 s -4.0 m 3
正向直线 0.6 m/s 0 rad/s 10 s 6.0 m 3
反向直线 -0.6 m/s 0 rad/s 10 s -6.0 m 3

建议按"正向 0.2 → 反向 0.2 → 正向 0.4 → 反向 0.4 → 正向 0.6 → 反向 0.6"的顺序测试。

2.4 正向直线 0.2 m/s

  • 运动时间:10 秒
  • 角速度:0 rad/s
  • 理论距离:2.0 m
  • 重复次数:3 次

第 1 次:FWD_STRAIGHT_V020_T10_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/forward/FWD_STRAIGHT_V020_T10_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 2 次:FWD_STRAIGHT_V020_T10_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/forward/FWD_STRAIGHT_V020_T10_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 3 次:FWD_STRAIGHT_V020_T10_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/forward/FWD_STRAIGHT_V020_T10_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

2.5 反向直线 -0.2 m/s

  • 运动时间:10 秒
  • 角速度:0 rad/s
  • 理论距离:-2.0 m
  • 重复次数:3 次

第 1 次:REV_STRAIGHT_V020_T10_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/reverse/REV_STRAIGHT_V020_T10_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 2 次:REV_STRAIGHT_V020_T10_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/reverse/REV_STRAIGHT_V020_T10_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 3 次:REV_STRAIGHT_V020_T10_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/reverse/REV_STRAIGHT_V020_T10_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

2.6 正向直线 0.4 m/s

  • 运动时间:10 秒
  • 角速度:0 rad/s
  • 理论距离:4.0 m
  • 重复次数:3 次

第 1 次:FWD_STRAIGHT_V040_T10_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/forward/FWD_STRAIGHT_V040_T10_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 2 次:FWD_STRAIGHT_V040_T10_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/forward/FWD_STRAIGHT_V040_T10_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 3 次:FWD_STRAIGHT_V040_T10_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/forward/FWD_STRAIGHT_V040_T10_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

2.7 反向直线 -0.4 m/s

  • 运动时间:10 秒
  • 角速度:0 rad/s
  • 理论距离:-4.0 m
  • 重复次数:3 次

第 1 次:REV_STRAIGHT_V040_T10_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/reverse/REV_STRAIGHT_V040_T10_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 2 次:REV_STRAIGHT_V040_T10_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/reverse/REV_STRAIGHT_V040_T10_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 3 次:REV_STRAIGHT_V040_T10_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/reverse/REV_STRAIGHT_V040_T10_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

2.8 正向直线 0.6 m/s

  • 运动时间:10 秒
  • 角速度:0 rad/s
  • 理论距离:6.0 m
  • 重复次数:3 次

只有在 0.2 m/s 和 0.4 m/s 工况运行安全且场地允许时,才执行本工况。

第 1 次:FWD_STRAIGHT_V060_T10_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/forward/FWD_STRAIGHT_V060_T10_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.6, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 2 次:FWD_STRAIGHT_V060_T10_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/forward/FWD_STRAIGHT_V060_T10_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.6, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 3 次:FWD_STRAIGHT_V060_T10_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/forward/FWD_STRAIGHT_V060_T10_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.6, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

2.9 反向直线 -0.6 m/s

  • 运动时间:10 秒
  • 角速度:0 rad/s
  • 理论距离:-6.0 m
  • 重复次数:3 次

只有在 0.2 m/s 和 0.4 m/s 工况运行安全且场地允许时,才执行本工况。

第 1 次:REV_STRAIGHT_V060_T10_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/reverse/REV_STRAIGHT_V060_T10_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.6, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 2 次:REV_STRAIGHT_V060_T10_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/reverse/REV_STRAIGHT_V060_T10_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.6, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 3 次:REV_STRAIGHT_V060_T10_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/02_straight/reverse/REV_STRAIGHT_V060_T10_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布直线运动速度 10 秒:

bash 复制代码
timeout --signal=INT 10s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.6, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第三部分:正向与反向圆周运动测试

3.1 作用

用于评价实际转弯半径、角速度响应、左右转向对称性、正反向转向对称性、圆周闭合误差和转弯过程中的轮胎侧滑。

3.2 参数

理论半径:

R=\\frac{\|v\|}{\|\\omega\|}=\\frac{0.3}{0.2}=1.5\\ \\mathrm{m}

理论一周时间:

T=\\frac{2\\pi}{\|\\omega\|}=\\frac{2\\pi}{0.2}\\approx31.42\\ \\mathrm{s}

  • 建议测试场地至少为 4 m × 4 m 的无障碍区域;
  • 每个工况重复 3 次;
  • 每次运动时间为 31.42 秒。

3.3 工况对应关系

正向工况 对应反向工况 理论半径
(0.3, 0.2) (-0.3, -0.2) 1.5 m
(0.3, -0.2) (-0.3, 0.2) 1.5 m

3.4 正向圆周:0.3 m/s,0.2 rad/s

  • 运动时间:31.42 秒
  • 理论半径:1.5 m
  • 重复次数:3 次

第 1 次:FWD_CIRCLE_V030_WP020_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/forward/FWD_CIRCLE_V030_WP020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

第 2 次:FWD_CIRCLE_V030_WP020_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/forward/FWD_CIRCLE_V030_WP020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

第 3 次:FWD_CIRCLE_V030_WP020_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/forward/FWD_CIRCLE_V030_WP020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

3.5 反向圆周:-0.3 m/s,-0.2 rad/s

  • 运动时间:31.42 秒
  • 理论半径:1.5 m
  • 重复次数:3 次

第 1 次:REV_CIRCLE_VN030_WN020_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/reverse/REV_CIRCLE_VN030_WN020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

第 2 次:REV_CIRCLE_VN030_WN020_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/reverse/REV_CIRCLE_VN030_WN020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

第 3 次:REV_CIRCLE_VN030_WN020_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/reverse/REV_CIRCLE_VN030_WN020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

3.6 正向圆周:0.3 m/s,-0.2 rad/s

  • 运动时间:31.42 秒
  • 理论半径:1.5 m
  • 重复次数:3 次

第 1 次:FWD_CIRCLE_V030_WN020_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/forward/FWD_CIRCLE_V030_WN020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

第 2 次:FWD_CIRCLE_V030_WN020_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/forward/FWD_CIRCLE_V030_WN020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

第 3 次:FWD_CIRCLE_V030_WN020_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/forward/FWD_CIRCLE_V030_WN020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

3.7 反向圆周:-0.3 m/s,0.2 rad/s

  • 运动时间:31.42 秒
  • 理论半径:1.5 m
  • 重复次数:3 次

第 1 次:REV_CIRCLE_VN030_WP020_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/reverse/REV_CIRCLE_VN030_WP020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

第 2 次:REV_CIRCLE_VN030_WP020_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/reverse/REV_CIRCLE_VN030_WP020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

第 3 次:REV_CIRCLE_VN030_WP020_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/03_circle/reverse/REV_CIRCLE_VN030_WP020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布圆周运动速度 31.42 秒:

bash 复制代码
timeout --signal=INT 31.42s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第一次执行该方向时,可先将运动时间临时改为 2 秒,以低速确认实际运动方向和转向方向。

第四部分:正向与反向启停响应测试

4.1 作用

用于记录速度指令开始后的启动延迟、加速过程、稳态速度、零速度指令后的制动过程、停车时间和停车附加距离。

4.2 工况

工况 运动速度 运动时间 零速度记录时间 次数
正向启停 0.4 m/s 5 s 8 s 5
反向启停 -0.4 m/s 5 s 8 s 5

4.3 正向启停

  • 运动时间:5 秒
  • 停车后零速度持续时间:8 秒
  • 重复次数:5 次

第 1 次:FWD_START_STOP_V040_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/04_start_stop/forward/FWD_START_STOP_V040_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布运动速度 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 8 秒:

bash 复制代码
timeout --signal=INT 8s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 2 次:FWD_START_STOP_V040_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/04_start_stop/forward/FWD_START_STOP_V040_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布运动速度 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 8 秒:

bash 复制代码
timeout --signal=INT 8s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 3 次:FWD_START_STOP_V040_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/04_start_stop/forward/FWD_START_STOP_V040_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布运动速度 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 8 秒:

bash 复制代码
timeout --signal=INT 8s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 4 次:FWD_START_STOP_V040_run04

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/04_start_stop/forward/FWD_START_STOP_V040_run04 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布运动速度 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 8 秒:

bash 复制代码
timeout --signal=INT 8s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 5 次:FWD_START_STOP_V040_run05

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/04_start_stop/forward/FWD_START_STOP_V040_run05 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布运动速度 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 8 秒:

bash 复制代码
timeout --signal=INT 8s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

4.4 反向启停

  • 运动时间:5 秒
  • 停车后零速度持续时间:8 秒
  • 重复次数:5 次

第 1 次:REV_START_STOP_V040_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/04_start_stop/reverse/REV_START_STOP_V040_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布运动速度 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 8 秒:

bash 复制代码
timeout --signal=INT 8s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 2 次:REV_START_STOP_V040_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/04_start_stop/reverse/REV_START_STOP_V040_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布运动速度 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 8 秒:

bash 复制代码
timeout --signal=INT 8s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 3 次:REV_START_STOP_V040_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/04_start_stop/reverse/REV_START_STOP_V040_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布运动速度 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 8 秒:

bash 复制代码
timeout --signal=INT 8s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 4 次:REV_START_STOP_V040_run04

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/04_start_stop/reverse/REV_START_STOP_V040_run04 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布运动速度 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 8 秒:

bash 复制代码
timeout --signal=INT 8s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第 5 次:REV_START_STOP_V040_run05

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/04_start_stop/reverse/REV_START_STOP_V040_run05 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

发布运动速度 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.4, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

停车并继续记录 8 秒:

bash 复制代码
timeout --signal=INT 8s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第五部分:正向与反向转向切换及回正测试

5.1 作用

用于评价左右转向切换、转向回正延迟、转向机构滞后、正反向回正差异,以及转向切换过程中是否出现车轮或车身摆动。

该测试始终保持非零线速度,不发布纯角速度,不要求双阿克曼底盘原地旋转。

5.2 正向转向切换过程

  1. 静止记录 5 秒;
  2. linear.x=0.3angular.z=0.2,持续 5 秒;
  3. linear.x=0.3angular.z=0,持续 3 秒;
  4. linear.x=0.3angular.z=-0.2,持续 5 秒;
  5. 发布零速度 5 秒;
  6. 重复 3 次。

正向第 1 次:FWD_STEER_SWITCH_V030_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/05_steering_switch/forward/FWD_STEER_SWITCH_V030_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

第一段转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

直线回正段 3 秒:

bash 复制代码
timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

反方向转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

正向第 2 次:FWD_STEER_SWITCH_V030_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/05_steering_switch/forward/FWD_STEER_SWITCH_V030_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

第一段转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

直线回正段 3 秒:

bash 复制代码
timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

反方向转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

正向第 3 次:FWD_STEER_SWITCH_V030_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/05_steering_switch/forward/FWD_STEER_SWITCH_V030_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

第一段转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

直线回正段 3 秒:

bash 复制代码
timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

反方向转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

5.3 反向转向切换过程

  1. 静止记录 5 秒;
  2. linear.x=-0.3angular.z=-0.2,持续 5 秒;
  3. linear.x=-0.3angular.z=0,持续 3 秒;
  4. linear.x=-0.3angular.z=0.2,持续 5 秒;
  5. 发布零速度 5 秒;
  6. 重复 3 次。

反向第 1 次:REV_STEER_SWITCH_V030_run01

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/05_steering_switch/reverse/REV_STEER_SWITCH_V030_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

第一段反向转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

反向直线回正段 3 秒:

bash 复制代码
timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

反方向转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

反向第 2 次:REV_STEER_SWITCH_V030_run02

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/05_steering_switch/reverse/REV_STEER_SWITCH_V030_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

第一段反向转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

反向直线回正段 3 秒:

bash 复制代码
timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

反方向转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

反向第 3 次:REV_STEER_SWITCH_V030_run03

终端 1:开始录包

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/05_steering_switch/reverse/REV_STEER_SWITCH_V030_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:按顺序执行本次测试对应的速度指令

bash 复制代码
sleep 5

第一段反向转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: -0.2}}"

反向直线回正段 3 秒:

bash 复制代码
timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

反方向转向 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.3, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.2}}"

停车并继续记录 5 秒:

bash 复制代码
timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:确认车辆已经停止后,按 Ctrl+C 结束本次录包。

第六部分:角速度增量自动阶梯响应测试

6.1 测试目的

该部分不再要求人工逐级执行每一个角度指令。终端 2 中的循环命令会自动连续发布完整阶梯序列,用于测试 Ranger Mini V3 对不同角速度增量的响应能力。

测试范围统一为:

\|\\omega_{\\mathrm{cmd}}\| \\leq 0.2\\ \\mathrm{rad/s}

每次测试自动完成:

  1. 0.00 rad/s 按固定增量升到 +0.20 rad/s
  2. +0.20 rad/s 按相同增量降回 0.00 rad/s
  3. 保持直线指令 3 秒;
  4. 0.00 rad/s 按固定增量降到 -0.20 rad/s
  5. -0.20 rad/s 按相同增量升回 0.00 rad/s
  6. 发布零速度 5 秒并结束测试。

/cmd_vel.angular.z 的单位是 rad/s,因此这里测试的是角速度指令增量 ,不是直接向底盘发布机械车轮转角。后续分析时,应以 bag 中实际记录到的新一级 /cmd_vel 首帧时间作为阶跃开始时刻。

6.2 测试变量

参数 设置
正向线速度 0.2 m/s
反向线速度 -0.2 m/s
最大角速度绝对值 0.2 rad/s
每一级保持时间 2 s
正负阶梯之间的回正时间 3 s
停车后记录时间 5 s
每个工况重复次数 3 次

6.3 需要测试的角速度增量

组别 单次增量 Δω 正向上升序列示例 主要作用
G01 0.01 rad/s 0.00, 0.01, ..., 0.20 检查小增量死区、微小指令能否立即执行
G02 0.02 rad/s 0.00, 0.02, ..., 0.20 检查小到中等增量的响应连续性
G05 0.05 rad/s 0.00, 0.05, 0.10, 0.15, 0.20 检查中等阶跃的延迟与超调
G10 0.10 rad/s 0.00, 0.10, 0.20 检查较大阶跃的快速响应能力
G20 0.20 rad/s 0.00, 0.20 检查允许范围内最大单次跳变

这五组可以判断:增量过小时是否落入死区,增量增大后延迟是否缩短,以及较大增量是否引起超调、摆动或转弯半径突变。

6.4 建立测试目录

bash 复制代码
mkdir -p ~/ranger_motion_test/06_angular_step/G01_DW001/forward
mkdir -p ~/ranger_motion_test/06_angular_step/G01_DW001/reverse
mkdir -p ~/ranger_motion_test/06_angular_step/G02_DW002/forward
mkdir -p ~/ranger_motion_test/06_angular_step/G02_DW002/reverse
mkdir -p ~/ranger_motion_test/06_angular_step/G05_DW005/forward
mkdir -p ~/ranger_motion_test/06_angular_step/G05_DW005/reverse
mkdir -p ~/ranger_motion_test/06_angular_step/G10_DW010/forward
mkdir -p ~/ranger_motion_test/06_angular_step/G10_DW010/reverse
mkdir -p ~/ranger_motion_test/06_angular_step/G20_DW020/forward
mkdir -p ~/ranger_motion_test/06_angular_step/G20_DW020/reverse

检查目录:

bash 复制代码
find ~/ranger_motion_test/06_angular_step -type d | sort

6.5 测试数量

增量组 正向次数 反向次数 Bag 数量
Δω=0.01 rad/s 3 3 6
Δω=0.02 rad/s 3 3 6
Δω=0.05 rad/s 3 3 6
Δω=0.10 rad/s 3 3 6
Δω=0.20 rad/s 3 3 6
合计 15 15 30

6.6 增量 Δω=0.01 rad/s

该组分别执行正向和反向测试,每个方向重复 3 次。终端命令会自动完成完整的正负角速度升降阶梯。

正向第 1 次:FWD_ANGULAR_STEP_G01_DW001_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G01_DW001/forward/FWD_ANGULAR_STEP_G01_DW001_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.01 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.19 -0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.01 -0.01 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.19 0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.01 rad/s 的正向自动阶梯测试。

正向第 2 次:FWD_ANGULAR_STEP_G01_DW001_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G01_DW001/forward/FWD_ANGULAR_STEP_G01_DW001_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.01 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.19 -0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.01 -0.01 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.19 0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.01 rad/s 的正向自动阶梯测试。

正向第 3 次:FWD_ANGULAR_STEP_G01_DW001_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G01_DW001/forward/FWD_ANGULAR_STEP_G01_DW001_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.01 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.19 -0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.01 -0.01 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.19 0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.01 rad/s 的正向自动阶梯测试。

反向第 1 次:REV_ANGULAR_STEP_G01_DW001_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G01_DW001/reverse/REV_ANGULAR_STEP_G01_DW001_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.01 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.19 0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.01 0.01 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.19 -0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

反向第 2 次:REV_ANGULAR_STEP_G01_DW001_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G01_DW001/reverse/REV_ANGULAR_STEP_G01_DW001_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.01 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.19 0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.01 0.01 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.19 -0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

反向第 3 次:REV_ANGULAR_STEP_G01_DW001_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G01_DW001/reverse/REV_ANGULAR_STEP_G01_DW001_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.01 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.19 0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.01 0.01 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.19 -0.01 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

6.7 增量 Δω=0.02 rad/s

该组分别执行正向和反向测试,每个方向重复 3 次。终端命令会自动完成完整的正负角速度升降阶梯。

正向第 1 次:FWD_ANGULAR_STEP_G02_DW002_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G02_DW002/forward/FWD_ANGULAR_STEP_G02_DW002_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.02 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.18 -0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.02 -0.02 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.18 0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.02 rad/s 的正向自动阶梯测试。

正向第 2 次:FWD_ANGULAR_STEP_G02_DW002_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G02_DW002/forward/FWD_ANGULAR_STEP_G02_DW002_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.02 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.18 -0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.02 -0.02 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.18 0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.02 rad/s 的正向自动阶梯测试。

正向第 3 次:FWD_ANGULAR_STEP_G02_DW002_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G02_DW002/forward/FWD_ANGULAR_STEP_G02_DW002_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.02 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.18 -0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.02 -0.02 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.18 0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.02 rad/s 的正向自动阶梯测试。

反向第 1 次:REV_ANGULAR_STEP_G02_DW002_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G02_DW002/reverse/REV_ANGULAR_STEP_G02_DW002_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.02 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.18 0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.02 0.02 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.18 -0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

反向第 2 次:REV_ANGULAR_STEP_G02_DW002_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G02_DW002/reverse/REV_ANGULAR_STEP_G02_DW002_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.02 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.18 0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.02 0.02 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.18 -0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

反向第 3 次:REV_ANGULAR_STEP_G02_DW002_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G02_DW002/reverse/REV_ANGULAR_STEP_G02_DW002_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.02 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.18 0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.02 0.02 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.18 -0.02 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

6.8 增量 Δω=0.05 rad/s

该组分别执行正向和反向测试,每个方向重复 3 次。终端命令会自动完成完整的正负角速度升降阶梯。

正向第 1 次:FWD_ANGULAR_STEP_G05_DW005_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G05_DW005/forward/FWD_ANGULAR_STEP_G05_DW005_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.05 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.15 -0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.05 -0.05 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.15 0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.05 rad/s 的正向自动阶梯测试。

正向第 2 次:FWD_ANGULAR_STEP_G05_DW005_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G05_DW005/forward/FWD_ANGULAR_STEP_G05_DW005_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.05 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.15 -0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.05 -0.05 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.15 0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.05 rad/s 的正向自动阶梯测试。

正向第 3 次:FWD_ANGULAR_STEP_G05_DW005_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G05_DW005/forward/FWD_ANGULAR_STEP_G05_DW005_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.05 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.15 -0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.05 -0.05 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.15 0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.05 rad/s 的正向自动阶梯测试。

反向第 1 次:REV_ANGULAR_STEP_G05_DW005_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G05_DW005/reverse/REV_ANGULAR_STEP_G05_DW005_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.05 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.15 0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.05 0.05 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.15 -0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

反向第 2 次:REV_ANGULAR_STEP_G05_DW005_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G05_DW005/reverse/REV_ANGULAR_STEP_G05_DW005_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.05 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.15 0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.05 0.05 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.15 -0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

反向第 3 次:REV_ANGULAR_STEP_G05_DW005_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G05_DW005/reverse/REV_ANGULAR_STEP_G05_DW005_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.05 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.15 0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.05 0.05 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.15 -0.05 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

6.9 增量 Δω=0.10 rad/s

该组分别执行正向和反向测试,每个方向重复 3 次。终端命令会自动完成完整的正负角速度升降阶梯。

正向第 1 次:FWD_ANGULAR_STEP_G10_DW010_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G10_DW010/forward/FWD_ANGULAR_STEP_G10_DW010_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.10 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.10 -0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.10 -0.10 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.10 0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.10 rad/s 的正向自动阶梯测试。

正向第 2 次:FWD_ANGULAR_STEP_G10_DW010_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G10_DW010/forward/FWD_ANGULAR_STEP_G10_DW010_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.10 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.10 -0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.10 -0.10 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.10 0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.10 rad/s 的正向自动阶梯测试。

正向第 3 次:FWD_ANGULAR_STEP_G10_DW010_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G10_DW010/forward/FWD_ANGULAR_STEP_G10_DW010_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.10 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.10 -0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.10 -0.10 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.10 0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.10 rad/s 的正向自动阶梯测试。

反向第 1 次:REV_ANGULAR_STEP_G10_DW010_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G10_DW010/reverse/REV_ANGULAR_STEP_G10_DW010_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.10 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.10 0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.10 0.10 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.10 -0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

反向第 2 次:REV_ANGULAR_STEP_G10_DW010_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G10_DW010/reverse/REV_ANGULAR_STEP_G10_DW010_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.10 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.10 0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.10 0.10 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.10 -0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

反向第 3 次:REV_ANGULAR_STEP_G10_DW010_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G10_DW010/reverse/REV_ANGULAR_STEP_G10_DW010_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.10 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.10 0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.10 0.10 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.10 -0.10 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

6.10 增量 Δω=0.20 rad/s

该组分别执行正向和反向测试,每个方向重复 3 次。终端命令会自动完成完整的正负角速度升降阶梯。

正向第 1 次:FWD_ANGULAR_STEP_G20_DW020_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G20_DW020/forward/FWD_ANGULAR_STEP_G20_DW020_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.20 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.20 -0.20 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.20 rad/s 的正向自动阶梯测试。

正向第 2 次:FWD_ANGULAR_STEP_G20_DW020_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G20_DW020/forward/FWD_ANGULAR_STEP_G20_DW020_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.20 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.20 -0.20 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.20 rad/s 的正向自动阶梯测试。

正向第 3 次:FWD_ANGULAR_STEP_G20_DW020_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G20_DW020/forward/FWD_ANGULAR_STEP_G20_DW020_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.20 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.20 -0.20 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.20 rad/s 的正向自动阶梯测试。

反向第 1 次:REV_ANGULAR_STEP_G20_DW020_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G20_DW020/reverse/REV_ANGULAR_STEP_G20_DW020_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.20 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.00 0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.20 0.20 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

反向第 2 次:REV_ANGULAR_STEP_G20_DW020_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G20_DW020/reverse/REV_ANGULAR_STEP_G20_DW020_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.20 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.00 0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.20 0.20 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

反向第 3 次:REV_ANGULAR_STEP_G20_DW020_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/06_angular_step/G20_DW020/reverse/REV_ANGULAR_STEP_G20_DW020_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
sleep 5

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.20 -0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" -0.00 0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 3s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.20 0.20 0.20); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

for w in $(LC_NUMERIC=C seq -f "%.2f" 0.00 -0.20 0.00); do
  timeout --signal=INT 2s ros2 topic pub -r 20 \
    /cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: -0.2, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: ${w}}}"
done

timeout --signal=INT 5s ros2 topic pub -r 20 \
  /cmd_vel geometry_msgs/msg/Twist \
  "{linear: {x: 0.0, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}"

终端 1:终端 2 的零速度指令结束、车辆完全停止后,按 Ctrl+C 停止录包。

反向时角速度符号与正向匹配工况相反,以保持相同的等效转向方向。测试前确认车辆后方安全。

6.11 本部分录包后重点检查的数据

后续统一处理 bag 时,应针对每一次角速度等级变化计算:

  • 新一级 /cmd_vel.angular.z 首帧时间;
  • /odom.twist.twist.angular.z 开始变化的时间;
  • /lio/odom 航向变化率开始变化的时间;
  • 指令到实际响应的延迟;
  • 实际角速度达到目标值 90% 所需时间;
  • 稳态角速度误差;
  • 角速度超调量;
  • 升阶和降阶响应是否一致;
  • 正角速度和负角速度响应是否一致;
  • 正向与反向响应是否一致;
  • angular.z=0 后的回正时间和残余横摆角速度。

终端循环在不同角速度等级之间会重新启动一次 ros2 topic pub 进程。后续延迟统计必须使用 bag 中实际记录到的 /cmd_vel 时间戳,不能只按名义上的每 2 秒切换时间计算。

第七部分:单节点连续变速与不规则曲线响应测试

7.1 修改目的

前六部分保持原样。本部分针对前六部分分析中发现的阶段切换空档,重新设计尚未录制的第七部分。

每次测试均在终端中启动一个持续存在的 ROS 2 发布节点。整个速度序列由同一个 publisher 以 20 Hz 连续发送,阶段切换时只修改 Twist 数值,不重新启动 ros2 topic pub 进程。

因此,第七部分可以用于评价真实的连续变速和连续曲率切换响应。

命令块直接粘贴到终端运行,不会在工作区中创建 Python 脚本文件。

7.2 测试作用

  • 线速度连续加速、减速时的响应延迟和超调;
  • 线速度变化过程中实际转弯半径是否合理;
  • 线速度和角速度同时变化时的耦合迟滞;
  • 正负角速度连续切换时的横摆超调和回正时间;
  • 低速大曲率、高速小曲率和高速大曲率的响应差异;
  • 正向与反向执行相同等效序列时的性能差异;
  • 为后续建立 cmd_vel → 实际速度/角速度 动态模型提供多工况数据。

7.3 安全限制

项目 限制
最大线速度绝对值 0.5 m/s
最大角速度绝对值 0.2 rad/s
发布频率 20 Hz
是否允许纯角速度 不允许
每个方向重复次数 3 次
建议场地 不小于 10 m × 10 m
  • 第一次执行每个 Profile 时,终端 3 必须保留紧急停车指令;
  • 反向测试前确认车辆后方和侧后方空间充足;
  • 每个 bag 只执行该小节对应的一个完整命令块;
  • 命令运行期间不要再启动其他 /cmd_vel 发布节点。

7.4 建立测试目录

bash 复制代码
mkdir -p ~/ranger_motion_test/07_continuous_variable/profile_A_straight_ramp/forward
mkdir -p ~/ranger_motion_test/07_continuous_variable/profile_A_straight_ramp/reverse
mkdir -p ~/ranger_motion_test/07_continuous_variable/profile_B_constant_radius/forward
mkdir -p ~/ranger_motion_test/07_continuous_variable/profile_B_constant_radius/reverse
mkdir -p ~/ranger_motion_test/07_continuous_variable/profile_C_irregular_curve/forward
mkdir -p ~/ranger_motion_test/07_continuous_variable/profile_C_irregular_curve/reverse
mkdir -p ~/ranger_motion_test/07_continuous_variable/profile_D_fast_switch/forward
mkdir -p ~/ranger_motion_test/07_continuous_variable/profile_D_fast_switch/reverse

检查目录:

bash 复制代码
find ~/ranger_motion_test/07_continuous_variable -type d | sort

7.5 测试数量

Profile 正向次数 反向次数 Bag 数量
A:连续直线加减速 3 3 6
B:近似恒定半径连续变速 3 3 6
C:连续不规则 S 形复合曲线 3 3 6
D:快速速度与曲率连续切换 3 3 6
合计 12 12 24

前六部分已经录制 79 个 bag。完成本部分后,累计为:

79+24=103\\ \\text{个 bag}

7.6 Profile A:连续直线加减速

angular.z=0 条件下,由同一个发布节点连续逐级加速和减速,用于得到不受发布节点重启影响的纵向动态基线。

速度序列表

段号 作用 正向 linear.x 正向 angular.z 持续时间 反向对应 linear.x 反向对应 angular.z
1 静止预记录 0.00 m/s 0.00 rad/s 5 s 0.00 m/s 0.00 rad/s
2 低速起步 0.10 m/s 0.00 rad/s 3 s -0.10 m/s 0.00 rad/s
3 加速至 0.20 m/s 0.20 m/s 0.00 rad/s 3 s -0.20 m/s 0.00 rad/s
4 加速至 0.30 m/s 0.30 m/s 0.00 rad/s 3 s -0.30 m/s 0.00 rad/s
5 加速至 0.40 m/s 0.40 m/s 0.00 rad/s 3 s -0.40 m/s 0.00 rad/s
6 加速至 0.50 m/s 0.50 m/s 0.00 rad/s 3 s -0.50 m/s 0.00 rad/s
7 减速至 0.40 m/s 0.40 m/s 0.00 rad/s 3 s -0.40 m/s 0.00 rad/s
8 减速至 0.30 m/s 0.30 m/s 0.00 rad/s 3 s -0.30 m/s 0.00 rad/s
9 减速至 0.20 m/s 0.20 m/s 0.00 rad/s 3 s -0.20 m/s 0.00 rad/s
10 减速至 0.10 m/s 0.10 m/s 0.00 rad/s 3 s -0.10 m/s 0.00 rad/s
11 主动零速度停车 0.00 m/s 0.00 rad/s 5 s 0.00 m/s 0.00 rad/s

Profile A 正向第 1 次:FWD_CONT_PROFILE_A_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_A_straight_ramp/forward/FWD_CONT_PROFILE_A_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_a_fwd_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("低速起步", 0.10000, 0.00000, 3.00),
("加速至 0.20 m/s", 0.20000, 0.00000, 3.00),
("加速至 0.30 m/s", 0.30000, 0.00000, 3.00),
("加速至 0.40 m/s", 0.40000, 0.00000, 3.00),
("加速至 0.50 m/s", 0.50000, 0.00000, 3.00),
("减速至 0.40 m/s", 0.40000, 0.00000, 3.00),
("减速至 0.30 m/s", 0.30000, 0.00000, 3.00),
("减速至 0.20 m/s", 0.20000, 0.00000, 3.00),
("减速至 0.10 m/s", 0.10000, 0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile A 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile A 正向第 2 次:FWD_CONT_PROFILE_A_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_A_straight_ramp/forward/FWD_CONT_PROFILE_A_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_a_fwd_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("低速起步", 0.10000, 0.00000, 3.00),
("加速至 0.20 m/s", 0.20000, 0.00000, 3.00),
("加速至 0.30 m/s", 0.30000, 0.00000, 3.00),
("加速至 0.40 m/s", 0.40000, 0.00000, 3.00),
("加速至 0.50 m/s", 0.50000, 0.00000, 3.00),
("减速至 0.40 m/s", 0.40000, 0.00000, 3.00),
("减速至 0.30 m/s", 0.30000, 0.00000, 3.00),
("减速至 0.20 m/s", 0.20000, 0.00000, 3.00),
("减速至 0.10 m/s", 0.10000, 0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile A 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile A 正向第 3 次:FWD_CONT_PROFILE_A_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_A_straight_ramp/forward/FWD_CONT_PROFILE_A_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_a_fwd_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("低速起步", 0.10000, 0.00000, 3.00),
("加速至 0.20 m/s", 0.20000, 0.00000, 3.00),
("加速至 0.30 m/s", 0.30000, 0.00000, 3.00),
("加速至 0.40 m/s", 0.40000, 0.00000, 3.00),
("加速至 0.50 m/s", 0.50000, 0.00000, 3.00),
("减速至 0.40 m/s", 0.40000, 0.00000, 3.00),
("减速至 0.30 m/s", 0.30000, 0.00000, 3.00),
("减速至 0.20 m/s", 0.20000, 0.00000, 3.00),
("减速至 0.10 m/s", 0.10000, 0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile A 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile A 反向第 1 次:REV_CONT_PROFILE_A_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_A_straight_ramp/reverse/REV_CONT_PROFILE_A_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_a_rev_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:低速起步", -0.10000, -0.00000, 3.00),
("反向对应:加速至 0.20 m/s", -0.20000, -0.00000, 3.00),
("反向对应:加速至 0.30 m/s", -0.30000, -0.00000, 3.00),
("反向对应:加速至 0.40 m/s", -0.40000, -0.00000, 3.00),
("反向对应:加速至 0.50 m/s", -0.50000, -0.00000, 3.00),
("反向对应:减速至 0.40 m/s", -0.40000, -0.00000, 3.00),
("反向对应:减速至 0.30 m/s", -0.30000, -0.00000, 3.00),
("反向对应:减速至 0.20 m/s", -0.20000, -0.00000, 3.00),
("反向对应:减速至 0.10 m/s", -0.10000, -0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

Profile A 反向第 2 次:REV_CONT_PROFILE_A_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_A_straight_ramp/reverse/REV_CONT_PROFILE_A_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_a_rev_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:低速起步", -0.10000, -0.00000, 3.00),
("反向对应:加速至 0.20 m/s", -0.20000, -0.00000, 3.00),
("反向对应:加速至 0.30 m/s", -0.30000, -0.00000, 3.00),
("反向对应:加速至 0.40 m/s", -0.40000, -0.00000, 3.00),
("反向对应:加速至 0.50 m/s", -0.50000, -0.00000, 3.00),
("反向对应:减速至 0.40 m/s", -0.40000, -0.00000, 3.00),
("反向对应:减速至 0.30 m/s", -0.30000, -0.00000, 3.00),
("反向对应:减速至 0.20 m/s", -0.20000, -0.00000, 3.00),
("反向对应:减速至 0.10 m/s", -0.10000, -0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

Profile A 反向第 3 次:REV_CONT_PROFILE_A_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_A_straight_ramp/reverse/REV_CONT_PROFILE_A_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_a_rev_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:低速起步", -0.10000, -0.00000, 3.00),
("反向对应:加速至 0.20 m/s", -0.20000, -0.00000, 3.00),
("反向对应:加速至 0.30 m/s", -0.30000, -0.00000, 3.00),
("反向对应:加速至 0.40 m/s", -0.40000, -0.00000, 3.00),
("反向对应:加速至 0.50 m/s", -0.50000, -0.00000, 3.00),
("反向对应:减速至 0.40 m/s", -0.40000, -0.00000, 3.00),
("反向对应:减速至 0.30 m/s", -0.30000, -0.00000, 3.00),
("反向对应:减速至 0.20 m/s", -0.20000, -0.00000, 3.00),
("反向对应:减速至 0.10 m/s", -0.10000, -0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

7.7 Profile B:近似恒定半径连续变速

保持 v/ω≈2.0 m,连续改变线速度和角速度。若底盘模型理想,理论曲率在变速过程中应近似不变。

速度序列表

段号 作用 正向 linear.x 正向 angular.z 持续时间 反向对应 linear.x 反向对应 angular.z
1 静止预记录 0.00 m/s 0.00 rad/s 5 s 0.00 m/s 0.00 rad/s
2 正曲率低速 0.10 m/s 0.05 rad/s 3 s -0.10 m/s -0.05 rad/s
3 正曲率加速 1 0.20 m/s 0.10 rad/s 3 s -0.20 m/s -0.10 rad/s
4 正曲率加速 2 0.30 m/s 0.15 rad/s 3 s -0.30 m/s -0.15 rad/s
5 正曲率最高速度 0.40 m/s 0.20 rad/s 3 s -0.40 m/s -0.20 rad/s
6 正曲率减速 1 0.30 m/s 0.15 rad/s 3 s -0.30 m/s -0.15 rad/s
7 正曲率减速 2 0.20 m/s 0.10 rad/s 3 s -0.20 m/s -0.10 rad/s
8 连续直线回正 0.20 m/s 0.00 rad/s 3 s -0.20 m/s 0.00 rad/s
9 负曲率低速 0.10 m/s -0.05 rad/s 3 s -0.10 m/s 0.05 rad/s
10 负曲率加速 1 0.20 m/s -0.10 rad/s 3 s -0.20 m/s 0.10 rad/s
11 负曲率加速 2 0.30 m/s -0.15 rad/s 3 s -0.30 m/s 0.15 rad/s
12 负曲率最高速度 0.40 m/s -0.20 rad/s 3 s -0.40 m/s 0.20 rad/s
13 负曲率减速 1 0.30 m/s -0.15 rad/s 3 s -0.30 m/s 0.15 rad/s
14 负曲率减速 2 0.20 m/s -0.10 rad/s 3 s -0.20 m/s 0.10 rad/s
15 主动零速度停车 0.00 m/s 0.00 rad/s 5 s 0.00 m/s 0.00 rad/s

Profile B 正向第 1 次:FWD_CONT_PROFILE_B_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_B_constant_radius/forward/FWD_CONT_PROFILE_B_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_b_fwd_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正曲率低速", 0.10000, 0.05000, 3.00),
("正曲率加速 1", 0.20000, 0.10000, 3.00),
("正曲率加速 2", 0.30000, 0.15000, 3.00),
("正曲率最高速度", 0.40000, 0.20000, 3.00),
("正曲率减速 1", 0.30000, 0.15000, 3.00),
("正曲率减速 2", 0.20000, 0.10000, 3.00),
("连续直线回正", 0.20000, 0.00000, 3.00),
("负曲率低速", 0.10000, -0.05000, 3.00),
("负曲率加速 1", 0.20000, -0.10000, 3.00),
("负曲率加速 2", 0.30000, -0.15000, 3.00),
("负曲率最高速度", 0.40000, -0.20000, 3.00),
("负曲率减速 1", 0.30000, -0.15000, 3.00),
("负曲率减速 2", 0.20000, -0.10000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile B 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile B 正向第 2 次:FWD_CONT_PROFILE_B_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_B_constant_radius/forward/FWD_CONT_PROFILE_B_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_b_fwd_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正曲率低速", 0.10000, 0.05000, 3.00),
("正曲率加速 1", 0.20000, 0.10000, 3.00),
("正曲率加速 2", 0.30000, 0.15000, 3.00),
("正曲率最高速度", 0.40000, 0.20000, 3.00),
("正曲率减速 1", 0.30000, 0.15000, 3.00),
("正曲率减速 2", 0.20000, 0.10000, 3.00),
("连续直线回正", 0.20000, 0.00000, 3.00),
("负曲率低速", 0.10000, -0.05000, 3.00),
("负曲率加速 1", 0.20000, -0.10000, 3.00),
("负曲率加速 2", 0.30000, -0.15000, 3.00),
("负曲率最高速度", 0.40000, -0.20000, 3.00),
("负曲率减速 1", 0.30000, -0.15000, 3.00),
("负曲率减速 2", 0.20000, -0.10000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile B 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile B 正向第 3 次:FWD_CONT_PROFILE_B_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_B_constant_radius/forward/FWD_CONT_PROFILE_B_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_b_fwd_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正曲率低速", 0.10000, 0.05000, 3.00),
("正曲率加速 1", 0.20000, 0.10000, 3.00),
("正曲率加速 2", 0.30000, 0.15000, 3.00),
("正曲率最高速度", 0.40000, 0.20000, 3.00),
("正曲率减速 1", 0.30000, 0.15000, 3.00),
("正曲率减速 2", 0.20000, 0.10000, 3.00),
("连续直线回正", 0.20000, 0.00000, 3.00),
("负曲率低速", 0.10000, -0.05000, 3.00),
("负曲率加速 1", 0.20000, -0.10000, 3.00),
("负曲率加速 2", 0.30000, -0.15000, 3.00),
("负曲率最高速度", 0.40000, -0.20000, 3.00),
("负曲率减速 1", 0.30000, -0.15000, 3.00),
("负曲率减速 2", 0.20000, -0.10000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile B 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile B 反向第 1 次:REV_CONT_PROFILE_B_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_B_constant_radius/reverse/REV_CONT_PROFILE_B_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_b_rev_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:正曲率低速", -0.10000, -0.05000, 3.00),
("反向对应:正曲率加速 1", -0.20000, -0.10000, 3.00),
("反向对应:正曲率加速 2", -0.30000, -0.15000, 3.00),
("反向对应:正曲率最高速度", -0.40000, -0.20000, 3.00),
("反向对应:正曲率减速 1", -0.30000, -0.15000, 3.00),
("反向对应:正曲率减速 2", -0.20000, -0.10000, 3.00),
("反向对应:连续直线回正", -0.20000, -0.00000, 3.00),
("反向对应:负曲率低速", -0.10000, 0.05000, 3.00),
("反向对应:负曲率加速 1", -0.20000, 0.10000, 3.00),
("反向对应:负曲率加速 2", -0.30000, 0.15000, 3.00),
("反向对应:负曲率最高速度", -0.40000, 0.20000, 3.00),
("反向对应:负曲率减速 1", -0.30000, 0.15000, 3.00),
("反向对应:负曲率减速 2", -0.20000, 0.10000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

Profile B 反向第 2 次:REV_CONT_PROFILE_B_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_B_constant_radius/reverse/REV_CONT_PROFILE_B_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_b_rev_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:正曲率低速", -0.10000, -0.05000, 3.00),
("反向对应:正曲率加速 1", -0.20000, -0.10000, 3.00),
("反向对应:正曲率加速 2", -0.30000, -0.15000, 3.00),
("反向对应:正曲率最高速度", -0.40000, -0.20000, 3.00),
("反向对应:正曲率减速 1", -0.30000, -0.15000, 3.00),
("反向对应:正曲率减速 2", -0.20000, -0.10000, 3.00),
("反向对应:连续直线回正", -0.20000, -0.00000, 3.00),
("反向对应:负曲率低速", -0.10000, 0.05000, 3.00),
("反向对应:负曲率加速 1", -0.20000, 0.10000, 3.00),
("反向对应:负曲率加速 2", -0.30000, 0.15000, 3.00),
("反向对应:负曲率最高速度", -0.40000, 0.20000, 3.00),
("反向对应:负曲率减速 1", -0.30000, 0.15000, 3.00),
("反向对应:负曲率减速 2", -0.20000, 0.10000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

Profile B 反向第 3 次:REV_CONT_PROFILE_B_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_B_constant_radius/reverse/REV_CONT_PROFILE_B_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_b_rev_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:正曲率低速", -0.10000, -0.05000, 3.00),
("反向对应:正曲率加速 1", -0.20000, -0.10000, 3.00),
("反向对应:正曲率加速 2", -0.30000, -0.15000, 3.00),
("反向对应:正曲率最高速度", -0.40000, -0.20000, 3.00),
("反向对应:正曲率减速 1", -0.30000, -0.15000, 3.00),
("反向对应:正曲率减速 2", -0.20000, -0.10000, 3.00),
("反向对应:连续直线回正", -0.20000, -0.00000, 3.00),
("反向对应:负曲率低速", -0.10000, 0.05000, 3.00),
("反向对应:负曲率加速 1", -0.20000, 0.10000, 3.00),
("反向对应:负曲率加速 2", -0.30000, 0.15000, 3.00),
("反向对应:负曲率最高速度", -0.40000, 0.20000, 3.00),
("反向对应:负曲率减速 1", -0.30000, 0.15000, 3.00),
("反向对应:负曲率减速 2", -0.20000, 0.10000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

7.8 Profile C:连续不规则 S 形复合曲线

连续改变速度、曲率大小和曲率符号,用于模拟导航控制器输出的非规则复合运动指令。

速度序列表

段号 作用 正向 linear.x 正向 angular.z 持续时间 反向对应 linear.x 反向对应 angular.z
1 静止预记录 0.00 m/s 0.00 rad/s 5 s 0.00 m/s 0.00 rad/s
2 低速小正曲率 0.15 m/s 0.04 rad/s 3 s -0.15 m/s -0.04 rad/s
3 加速并增大正曲率 0.30 m/s 0.12 rad/s 3 s -0.30 m/s -0.12 rad/s
4 高速小正曲率 0.50 m/s 0.08 rad/s 3 s -0.50 m/s -0.08 rad/s
5 减速并切换大负曲率 0.25 m/s -0.18 rad/s 3 s -0.25 m/s 0.18 rad/s
6 加速并减小负曲率 0.40 m/s -0.05 rad/s 3 s -0.40 m/s 0.05 rad/s
7 低速最大正角速度 0.20 m/s 0.20 rad/s 3 s -0.20 m/s -0.20 rad/s
8 高速负曲率 0.45 m/s -0.12 rad/s 3 s -0.45 m/s 0.12 rad/s
9 连续直线回正 0.30 m/s 0.00 rad/s 3 s -0.30 m/s 0.00 rad/s
10 主动零速度停车 0.00 m/s 0.00 rad/s 5 s 0.00 m/s 0.00 rad/s

Profile C 正向第 1 次:FWD_CONT_PROFILE_C_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_C_irregular_curve/forward/FWD_CONT_PROFILE_C_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_c_fwd_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("低速小正曲率", 0.15000, 0.04000, 3.00),
("加速并增大正曲率", 0.30000, 0.12000, 3.00),
("高速小正曲率", 0.50000, 0.08000, 3.00),
("减速并切换大负曲率", 0.25000, -0.18000, 3.00),
("加速并减小负曲率", 0.40000, -0.05000, 3.00),
("低速最大正角速度", 0.20000, 0.20000, 3.00),
("高速负曲率", 0.45000, -0.12000, 3.00),
("连续直线回正", 0.30000, 0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile C 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile C 正向第 2 次:FWD_CONT_PROFILE_C_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_C_irregular_curve/forward/FWD_CONT_PROFILE_C_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_c_fwd_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("低速小正曲率", 0.15000, 0.04000, 3.00),
("加速并增大正曲率", 0.30000, 0.12000, 3.00),
("高速小正曲率", 0.50000, 0.08000, 3.00),
("减速并切换大负曲率", 0.25000, -0.18000, 3.00),
("加速并减小负曲率", 0.40000, -0.05000, 3.00),
("低速最大正角速度", 0.20000, 0.20000, 3.00),
("高速负曲率", 0.45000, -0.12000, 3.00),
("连续直线回正", 0.30000, 0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile C 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile C 正向第 3 次:FWD_CONT_PROFILE_C_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_C_irregular_curve/forward/FWD_CONT_PROFILE_C_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_c_fwd_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("低速小正曲率", 0.15000, 0.04000, 3.00),
("加速并增大正曲率", 0.30000, 0.12000, 3.00),
("高速小正曲率", 0.50000, 0.08000, 3.00),
("减速并切换大负曲率", 0.25000, -0.18000, 3.00),
("加速并减小负曲率", 0.40000, -0.05000, 3.00),
("低速最大正角速度", 0.20000, 0.20000, 3.00),
("高速负曲率", 0.45000, -0.12000, 3.00),
("连续直线回正", 0.30000, 0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile C 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile C 反向第 1 次:REV_CONT_PROFILE_C_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_C_irregular_curve/reverse/REV_CONT_PROFILE_C_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_c_rev_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:低速小正曲率", -0.15000, -0.04000, 3.00),
("反向对应:加速并增大正曲率", -0.30000, -0.12000, 3.00),
("反向对应:高速小正曲率", -0.50000, -0.08000, 3.00),
("反向对应:减速并切换大负曲率", -0.25000, 0.18000, 3.00),
("反向对应:加速并减小负曲率", -0.40000, 0.05000, 3.00),
("反向对应:低速最大正角速度", -0.20000, -0.20000, 3.00),
("反向对应:高速负曲率", -0.45000, 0.12000, 3.00),
("反向对应:连续直线回正", -0.30000, -0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

Profile C 反向第 2 次:REV_CONT_PROFILE_C_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_C_irregular_curve/reverse/REV_CONT_PROFILE_C_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_c_rev_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:低速小正曲率", -0.15000, -0.04000, 3.00),
("反向对应:加速并增大正曲率", -0.30000, -0.12000, 3.00),
("反向对应:高速小正曲率", -0.50000, -0.08000, 3.00),
("反向对应:减速并切换大负曲率", -0.25000, 0.18000, 3.00),
("反向对应:加速并减小负曲率", -0.40000, 0.05000, 3.00),
("反向对应:低速最大正角速度", -0.20000, -0.20000, 3.00),
("反向对应:高速负曲率", -0.45000, 0.12000, 3.00),
("反向对应:连续直线回正", -0.30000, -0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

Profile C 反向第 3 次:REV_CONT_PROFILE_C_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_C_irregular_curve/reverse/REV_CONT_PROFILE_C_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_c_rev_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:低速小正曲率", -0.15000, -0.04000, 3.00),
("反向对应:加速并增大正曲率", -0.30000, -0.12000, 3.00),
("反向对应:高速小正曲率", -0.50000, -0.08000, 3.00),
("反向对应:减速并切换大负曲率", -0.25000, 0.18000, 3.00),
("反向对应:加速并减小负曲率", -0.40000, 0.05000, 3.00),
("反向对应:低速最大正角速度", -0.20000, -0.20000, 3.00),
("反向对应:高速负曲率", -0.45000, 0.12000, 3.00),
("反向对应:连续直线回正", -0.30000, -0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

7.9 Profile D:快速速度与曲率连续切换

每段保持 2 秒,在相邻阶段之间进行较大幅度的速度和角速度变化,用于测试短时动态、转向超调和连续切换稳定性。

速度序列表

段号 作用 正向 linear.x 正向 angular.z 持续时间 反向对应 linear.x 反向对应 angular.z
1 静止预记录 0.00 m/s 0.00 rad/s 5 s 0.00 m/s 0.00 rad/s
2 中低速直线 0.20 m/s 0.00 rad/s 2 s -0.20 m/s 0.00 rad/s
3 高速最大正角速度 0.50 m/s 0.20 rad/s 2 s -0.50 m/s -0.20 rad/s
4 低速最大负角速度 0.15 m/s -0.20 rad/s 2 s -0.15 m/s 0.20 rad/s
5 中高速小正角速度 0.40 m/s 0.05 rad/s 2 s -0.40 m/s -0.05 rad/s
6 中低速负角速度 0.25 m/s -0.10 rad/s 2 s -0.25 m/s 0.10 rad/s
7 高速最大负角速度 0.50 m/s -0.20 rad/s 2 s -0.50 m/s 0.20 rad/s
8 低速较大正角速度 0.20 m/s 0.15 rad/s 2 s -0.20 m/s -0.15 rad/s
9 中速连续回正 0.35 m/s 0.00 rad/s 2 s -0.35 m/s 0.00 rad/s
10 主动零速度停车 0.00 m/s 0.00 rad/s 5 s 0.00 m/s 0.00 rad/s

Profile D 正向第 1 次:FWD_CONT_PROFILE_D_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_D_fast_switch/forward/FWD_CONT_PROFILE_D_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_d_fwd_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("中低速直线", 0.20000, 0.00000, 2.00),
("高速最大正角速度", 0.50000, 0.20000, 2.00),
("低速最大负角速度", 0.15000, -0.20000, 2.00),
("中高速小正角速度", 0.40000, 0.05000, 2.00),
("中低速负角速度", 0.25000, -0.10000, 2.00),
("高速最大负角速度", 0.50000, -0.20000, 2.00),
("低速较大正角速度", 0.20000, 0.15000, 2.00),
("中速连续回正", 0.35000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile D 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile D 正向第 2 次:FWD_CONT_PROFILE_D_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_D_fast_switch/forward/FWD_CONT_PROFILE_D_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_d_fwd_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("中低速直线", 0.20000, 0.00000, 2.00),
("高速最大正角速度", 0.50000, 0.20000, 2.00),
("低速最大负角速度", 0.15000, -0.20000, 2.00),
("中高速小正角速度", 0.40000, 0.05000, 2.00),
("中低速负角速度", 0.25000, -0.10000, 2.00),
("高速最大负角速度", 0.50000, -0.20000, 2.00),
("低速较大正角速度", 0.20000, 0.15000, 2.00),
("中速连续回正", 0.35000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile D 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile D 正向第 3 次:FWD_CONT_PROFILE_D_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_D_fast_switch/forward/FWD_CONT_PROFILE_D_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_d_fwd_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("中低速直线", 0.20000, 0.00000, 2.00),
("高速最大正角速度", 0.50000, 0.20000, 2.00),
("低速最大负角速度", 0.15000, -0.20000, 2.00),
("中高速小正角速度", 0.40000, 0.05000, 2.00),
("中低速负角速度", 0.25000, -0.10000, 2.00),
("高速最大负角速度", 0.50000, -0.20000, 2.00),
("低速较大正角速度", 0.20000, 0.15000, 2.00),
("中速连续回正", 0.35000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只执行 Profile D 的正向连续序列。整个过程中只有一个 /cmd_vel 发布节点。

Profile D 反向第 1 次:REV_CONT_PROFILE_D_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_D_fast_switch/reverse/REV_CONT_PROFILE_D_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_d_rev_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:中低速直线", -0.20000, -0.00000, 2.00),
("反向对应:高速最大正角速度", -0.50000, -0.20000, 2.00),
("反向对应:低速最大负角速度", -0.15000, 0.20000, 2.00),
("反向对应:中高速小正角速度", -0.40000, -0.05000, 2.00),
("反向对应:中低速负角速度", -0.25000, 0.10000, 2.00),
("反向对应:高速最大负角速度", -0.50000, 0.20000, 2.00),
("反向对应:低速较大正角速度", -0.20000, -0.15000, 2.00),
("反向对应:中速连续回正", -0.35000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

Profile D 反向第 2 次:REV_CONT_PROFILE_D_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_D_fast_switch/reverse/REV_CONT_PROFILE_D_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_d_rev_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:中低速直线", -0.20000, -0.00000, 2.00),
("反向对应:高速最大正角速度", -0.50000, -0.20000, 2.00),
("反向对应:低速最大负角速度", -0.15000, 0.20000, 2.00),
("反向对应:中高速小正角速度", -0.40000, -0.05000, 2.00),
("反向对应:中低速负角速度", -0.25000, 0.10000, 2.00),
("反向对应:高速最大负角速度", -0.50000, 0.20000, 2.00),
("反向对应:低速较大正角速度", -0.20000, -0.15000, 2.00),
("反向对应:中速连续回正", -0.35000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

Profile D 反向第 3 次:REV_CONT_PROFILE_D_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/07_continuous_variable/profile_D_fast_switch/reverse/REV_CONT_PROFILE_D_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_profile_d_rev_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:中低速直线", -0.20000, -0.00000, 2.00),
("反向对应:高速最大正角速度", -0.50000, -0.20000, 2.00),
("反向对应:低速最大负角速度", -0.15000, 0.20000, 2.00),
("反向对应:中高速小正角速度", -0.40000, -0.05000, 2.00),
("反向对应:中低速负角速度", -0.25000, 0.10000, 2.00),
("反向对应:高速最大负角速度", -0.50000, 0.20000, 2.00),
("反向对应:低速较大正角速度", -0.20000, -0.15000, 2.00),
("反向对应:中速连续回正", -0.35000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向序列同时反转线速度和角速度,以保持对应的等效转向关系。

7.10 第七部分录制后的完整性判据

第七部分每个 bag 应满足:

  • /cmd_vel 在整个运动阶段持续约 20 Hz;
  • 相邻阶段的 /cmd_vel 时间间隔不应出现约 1 s 的发布节点重启空档;
  • 正常情况下,相邻消息最大间隔应接近 0.05 s,允许少量系统调度波动;
  • 每个阶段的真实持续时间应接近速度序列表中的设置值;
  • 最终主动零速度阶段应直接紧接在最后一个非零指令之后。

录包后可先检查消息频率:

bash 复制代码
ros2 bag play <对应的bag目录>

# 另一个终端检查
ros2 topic hz /cmd_vel

第八部分:前六部分缺失物理工况的连续发布补充测试

8.1 增加本部分的原因

根据前六部分 bag 分析,仍缺少三类能够直接支撑底盘动态结论的数据:

  1. 非零速度之后立即、主动发布零速度时的真实制动响应;
  2. 车辆保持运动时,左转---回正---右转的连续切换响应;
  3. 车辆保持恒定线速度时,angular.z0~0.2 rad/s 范围内按不同增量连续变化的响应。

本部分不替换、删除或覆盖前六部分已有 bag。所有补充数据使用新的目录和包名。

8.2 建立补充测试目录

bash 复制代码
mkdir -p ~/ranger_motion_test/08_completion/active_zero_brake/forward
mkdir -p ~/ranger_motion_test/08_completion/active_zero_brake/reverse
mkdir -p ~/ranger_motion_test/08_completion/continuous_steer_switch/forward
mkdir -p ~/ranger_motion_test/08_completion/continuous_steer_switch/reverse
mkdir -p ~/ranger_motion_test/08_completion/continuous_angular_step/G01_DW001/forward
mkdir -p ~/ranger_motion_test/08_completion/continuous_angular_step/G01_DW001/reverse
mkdir -p ~/ranger_motion_test/08_completion/continuous_angular_step/G02_DW002/forward
mkdir -p ~/ranger_motion_test/08_completion/continuous_angular_step/G02_DW002/reverse
mkdir -p ~/ranger_motion_test/08_completion/continuous_angular_step/G05_DW005/forward
mkdir -p ~/ranger_motion_test/08_completion/continuous_angular_step/G05_DW005/reverse
mkdir -p ~/ranger_motion_test/08_completion/continuous_angular_step/G10_DW010/forward
mkdir -p ~/ranger_motion_test/08_completion/continuous_angular_step/G10_DW010/reverse
mkdir -p ~/ranger_motion_test/08_completion/continuous_angular_step/G20_DW020/forward
mkdir -p ~/ranger_motion_test/08_completion/continuous_angular_step/G20_DW020/reverse

检查目录:

bash 复制代码
find ~/ranger_motion_test/08_completion -type d | sort

8.3 补充测试数量

补充工况 工况数量 单工况次数 Bag 数量
主动零速度制动:正向/反向 0.2、0.4、0.5 m/s 6 3 18
连续左转---回正---右转:正向/反向 2 3 6
连续角速度阶梯:5 种增量,正向/反向 10 3 30
合计 18 --- 54

完成第七、八部分后,全部数据量为:

79+24+54=157\\ \\text{个 bag}

8.4 主动零速度制动测试

8.4.1 测试目的

车辆由同一个 publisher 持续接收非零速度,随后在下一个 20 Hz 周期内直接切换为零速度。该测试用于与第四部分的命令断流/看门狗停车结果区分。

8.4.2 工况

方向 速度 非零速度时间 主动零速度时间 次数
正向 0.2 m/s 5 s 5 s 3
反向 -0.2 m/s 5 s 5 s 3
正向 0.4 m/s 5 s 5 s 3
反向 -0.4 m/s 5 s 5 s 3
正向 0.5 m/s 5 s 5 s 3
反向 -0.5 m/s 5 s 5 s 3

8.4.3 正向 0.2 m/s 主动停车

第 1 次:FWD_ACTIVE_ZERO_BRAKE_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/forward/FWD_ACTIVE_ZERO_BRAKE_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_fwd_v020_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正向 0.2 m/s", 0.20000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

非零速度与零速度由同一 publisher 连续发送,可直接统计主动零速度后的制动延迟和制动距离。

第 2 次:FWD_ACTIVE_ZERO_BRAKE_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/forward/FWD_ACTIVE_ZERO_BRAKE_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_fwd_v020_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正向 0.2 m/s", 0.20000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

非零速度与零速度由同一 publisher 连续发送,可直接统计主动零速度后的制动延迟和制动距离。

第 3 次:FWD_ACTIVE_ZERO_BRAKE_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/forward/FWD_ACTIVE_ZERO_BRAKE_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_fwd_v020_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正向 0.2 m/s", 0.20000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

非零速度与零速度由同一 publisher 连续发送,可直接统计主动零速度后的制动延迟和制动距离。

8.4.4 反向 -0.2 m/s 主动停车

第 1 次:REV_ACTIVE_ZERO_BRAKE_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/reverse/REV_ACTIVE_ZERO_BRAKE_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_rev_v020_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向 -0.2 m/s", -0.20000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方安全。

第 2 次:REV_ACTIVE_ZERO_BRAKE_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/reverse/REV_ACTIVE_ZERO_BRAKE_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_rev_v020_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向 -0.2 m/s", -0.20000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方安全。

第 3 次:REV_ACTIVE_ZERO_BRAKE_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/reverse/REV_ACTIVE_ZERO_BRAKE_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_rev_v020_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向 -0.2 m/s", -0.20000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方安全。

8.4.5 正向 0.4 m/s 主动停车

第 1 次:FWD_ACTIVE_ZERO_BRAKE_V040_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/forward/FWD_ACTIVE_ZERO_BRAKE_V040_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_fwd_v040_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正向 0.4 m/s", 0.40000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

非零速度与零速度由同一 publisher 连续发送,可直接统计主动零速度后的制动延迟和制动距离。

第 2 次:FWD_ACTIVE_ZERO_BRAKE_V040_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/forward/FWD_ACTIVE_ZERO_BRAKE_V040_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_fwd_v040_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正向 0.4 m/s", 0.40000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

非零速度与零速度由同一 publisher 连续发送,可直接统计主动零速度后的制动延迟和制动距离。

第 3 次:FWD_ACTIVE_ZERO_BRAKE_V040_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/forward/FWD_ACTIVE_ZERO_BRAKE_V040_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_fwd_v040_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正向 0.4 m/s", 0.40000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

非零速度与零速度由同一 publisher 连续发送,可直接统计主动零速度后的制动延迟和制动距离。

8.4.6 反向 -0.4 m/s 主动停车

第 1 次:REV_ACTIVE_ZERO_BRAKE_V040_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/reverse/REV_ACTIVE_ZERO_BRAKE_V040_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_rev_v040_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向 -0.4 m/s", -0.40000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方安全。

第 2 次:REV_ACTIVE_ZERO_BRAKE_V040_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/reverse/REV_ACTIVE_ZERO_BRAKE_V040_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_rev_v040_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向 -0.4 m/s", -0.40000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方安全。

第 3 次:REV_ACTIVE_ZERO_BRAKE_V040_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/reverse/REV_ACTIVE_ZERO_BRAKE_V040_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_rev_v040_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向 -0.4 m/s", -0.40000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方安全。

8.4.7 正向 0.5 m/s 主动停车

第 1 次:FWD_ACTIVE_ZERO_BRAKE_V050_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/forward/FWD_ACTIVE_ZERO_BRAKE_V050_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_fwd_v050_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正向 0.5 m/s", 0.50000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

非零速度与零速度由同一 publisher 连续发送,可直接统计主动零速度后的制动延迟和制动距离。

第 2 次:FWD_ACTIVE_ZERO_BRAKE_V050_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/forward/FWD_ACTIVE_ZERO_BRAKE_V050_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_fwd_v050_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正向 0.5 m/s", 0.50000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

非零速度与零速度由同一 publisher 连续发送,可直接统计主动零速度后的制动延迟和制动距离。

第 3 次:FWD_ACTIVE_ZERO_BRAKE_V050_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/forward/FWD_ACTIVE_ZERO_BRAKE_V050_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_fwd_v050_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正向 0.5 m/s", 0.50000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

非零速度与零速度由同一 publisher 连续发送,可直接统计主动零速度后的制动延迟和制动距离。

8.4.8 反向 -0.5 m/s 主动停车

第 1 次:REV_ACTIVE_ZERO_BRAKE_V050_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/reverse/REV_ACTIVE_ZERO_BRAKE_V050_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_rev_v050_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向 -0.5 m/s", -0.50000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方安全。

第 2 次:REV_ACTIVE_ZERO_BRAKE_V050_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/reverse/REV_ACTIVE_ZERO_BRAKE_V050_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_rev_v050_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向 -0.5 m/s", -0.50000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方安全。

第 3 次:REV_ACTIVE_ZERO_BRAKE_V050_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/active_zero_brake/reverse/REV_ACTIVE_ZERO_BRAKE_V050_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_active_brake_rev_v050_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向 -0.5 m/s", -0.50000, 0.00000, 5.00),
("立即主动发布零速度", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方安全。

8.5 保持运动状态下的连续转向切换与回正

8.5.1 测试目的

该测试补齐第五部分缺少的连续转向切换数据。车辆在整个转向序列中保持|linear.x|=0.3 m/s,不会在左转、回正和右转之间停车。

正向序列:

  1. 零速度预记录 5 s;
  2. v=0.3、ω=0.2,持续 5 s;
  3. v=0.3、ω=0,持续 3 s;
  4. v=0.3、ω=-0.2,持续 5 s;
  5. v=0.3、ω=0,持续 3 s;
  6. 主动零速度 5 s。

反向序列同时反转 linear.xangular.z

8.5.2 正向连续转向切换

正向第 1 次:FWD_CONT_STEER_SWITCH_V030_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_steer_switch/forward/FWD_CONT_STEER_SWITCH_V030_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_steer_fwd_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("连续正向第一转向", 0.30000, 0.20000, 5.00),
("保持运动并回正", 0.30000, 0.00000, 3.00),
("连续切换至反方向转向", 0.30000, -0.20000, 5.00),
("保持运动并再次回正", 0.30000, 0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本次测试用于统计连续回正和角速度换向,不允许中途启动其他发布节点。

正向第 2 次:FWD_CONT_STEER_SWITCH_V030_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_steer_switch/forward/FWD_CONT_STEER_SWITCH_V030_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_steer_fwd_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("连续正向第一转向", 0.30000, 0.20000, 5.00),
("保持运动并回正", 0.30000, 0.00000, 3.00),
("连续切换至反方向转向", 0.30000, -0.20000, 5.00),
("保持运动并再次回正", 0.30000, 0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本次测试用于统计连续回正和角速度换向,不允许中途启动其他发布节点。

正向第 3 次:FWD_CONT_STEER_SWITCH_V030_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_steer_switch/forward/FWD_CONT_STEER_SWITCH_V030_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_steer_fwd_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("连续正向第一转向", 0.30000, 0.20000, 5.00),
("保持运动并回正", 0.30000, 0.00000, 3.00),
("连续切换至反方向转向", 0.30000, -0.20000, 5.00),
("保持运动并再次回正", 0.30000, 0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本次测试用于统计连续回正和角速度换向,不允许中途启动其他发布节点。

8.5.3 反向连续转向切换

反向第 1 次:REV_CONT_STEER_SWITCH_V030_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_steer_switch/reverse/REV_CONT_STEER_SWITCH_V030_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_steer_rev_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:连续正向第一转向", -0.30000, -0.20000, 5.00),
("反向对应:保持运动并回正", -0.30000, -0.00000, 3.00),
("反向对应:连续切换至反方向转向", -0.30000, 0.20000, 5.00),
("反向对应:保持运动并再次回正", -0.30000, -0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方和侧后方空间充足。

反向第 2 次:REV_CONT_STEER_SWITCH_V030_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_steer_switch/reverse/REV_CONT_STEER_SWITCH_V030_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_steer_rev_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:连续正向第一转向", -0.30000, -0.20000, 5.00),
("反向对应:保持运动并回正", -0.30000, -0.00000, 3.00),
("反向对应:连续切换至反方向转向", -0.30000, 0.20000, 5.00),
("反向对应:保持运动并再次回正", -0.30000, -0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方和侧后方空间充足。

反向第 3 次:REV_CONT_STEER_SWITCH_V030_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_steer_switch/reverse/REV_CONT_STEER_SWITCH_V030_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_steer_rev_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("反向对应:连续正向第一转向", -0.30000, -0.20000, 5.00),
("反向对应:保持运动并回正", -0.30000, -0.00000, 3.00),
("反向对应:连续切换至反方向转向", -0.30000, 0.20000, 5.00),
("反向对应:保持运动并再次回正", -0.30000, -0.00000, 3.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

测试前确认车辆后方和侧后方空间充足。

8.6 保持运动状态下的连续角速度增量复核

8.6.1 测试目的

该测试保留第六部分原始 bag,同时补录真正连续的角速度阶梯。车辆在线速度恒定的情况下,angular.z-0.2~0.2 rad/s 范围内连续升降,阶段之间不重新创建 publisher。

测试增量为:

组别 单次角速度增量 主要判断
G01 0.01 rad/s 微小增量死区和最低可靠响应阈值
G02 0.02 rad/s 小增量连续跟随能力
G05 0.05 rad/s 中等增量的延迟、误差和超调
G10 0.10 rad/s 较大增量的快速响应能力
G20 0.20 rad/s 允许范围内最大单次阶跃

统一设置:

  • 正向线速度:0.2 m/s
  • 反向线速度:-0.2 m/s
  • 每个角速度等级保持:2 s
  • 正、负阶梯之间保持直线:3 s
  • 最终主动零速度:5 s
  • 每组正向、反向各重复 3 次。

8.6.2 连续增量 Δω=0.01 rad/s

正向上升等级:0.00, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.09, 0.10, 0.11, 0.12, 0.13, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.20 rad/s

随后按相同等级降回 0,保持运动直线 3 秒,再对负角速度执行同样的连续升降序列。

正向第 1 次:FWD_CONT_ANGULAR_STEP_G01_DW001_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G01_DW001/forward/FWD_CONT_ANGULAR_STEP_G01_DW001_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g01_dw001_fwd_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.01 rad/s", 0.20000, 0.01000, 2.00),
("正等效方向上升至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向上升至 0.03 rad/s", 0.20000, 0.03000, 2.00),
("正等效方向上升至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向上升至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向上升至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向上升至 0.07 rad/s", 0.20000, 0.07000, 2.00),
("正等效方向上升至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向上升至 0.09 rad/s", 0.20000, 0.09000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.11 rad/s", 0.20000, 0.11000, 2.00),
("正等效方向上升至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向上升至 0.13 rad/s", 0.20000, 0.13000, 2.00),
("正等效方向上升至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向上升至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向上升至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向上升至 0.17 rad/s", 0.20000, 0.17000, 2.00),
("正等效方向上升至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向上升至 0.19 rad/s", 0.20000, 0.19000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.19 rad/s", 0.20000, 0.19000, 2.00),
("正等效方向下降至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向下降至 0.17 rad/s", 0.20000, 0.17000, 2.00),
("正等效方向下降至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向下降至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向下降至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向下降至 0.13 rad/s", 0.20000, 0.13000, 2.00),
("正等效方向下降至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向下降至 0.11 rad/s", 0.20000, 0.11000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.09 rad/s", 0.20000, 0.09000, 2.00),
("正等效方向下降至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向下降至 0.07 rad/s", 0.20000, 0.07000, 2.00),
("正等效方向下降至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向下降至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向下降至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向下降至 0.03 rad/s", 0.20000, 0.03000, 2.00),
("正等效方向下降至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向下降至 0.01 rad/s", 0.20000, 0.01000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.01 rad/s", 0.20000, -0.01000, 2.00),
("负等效方向上升至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向上升至 -0.03 rad/s", 0.20000, -0.03000, 2.00),
("负等效方向上升至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向上升至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向上升至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向上升至 -0.07 rad/s", 0.20000, -0.07000, 2.00),
("负等效方向上升至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向上升至 -0.09 rad/s", 0.20000, -0.09000, 2.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.11 rad/s", 0.20000, -0.11000, 2.00),
("负等效方向上升至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向上升至 -0.13 rad/s", 0.20000, -0.13000, 2.00),
("负等效方向上升至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向上升至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向上升至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向上升至 -0.17 rad/s", 0.20000, -0.17000, 2.00),
("负等效方向上升至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向上升至 -0.19 rad/s", 0.20000, -0.19000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.19 rad/s", 0.20000, -0.19000, 2.00),
("负等效方向下降至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向下降至 -0.17 rad/s", 0.20000, -0.17000, 2.00),
("负等效方向下降至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向下降至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向下降至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向下降至 -0.13 rad/s", 0.20000, -0.13000, 2.00),
("负等效方向下降至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向下降至 -0.11 rad/s", 0.20000, -0.11000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.09 rad/s", 0.20000, -0.09000, 2.00),
("负等效方向下降至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向下降至 -0.07 rad/s", 0.20000, -0.07000, 2.00),
("负等效方向下降至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向下降至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向下降至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向下降至 -0.03 rad/s", 0.20000, -0.03000, 2.00),
("负等效方向下降至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向下降至 -0.01 rad/s", 0.20000, -0.01000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.01 rad/s 的正向连续阶梯。

正向第 2 次:FWD_CONT_ANGULAR_STEP_G01_DW001_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G01_DW001/forward/FWD_CONT_ANGULAR_STEP_G01_DW001_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g01_dw001_fwd_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.01 rad/s", 0.20000, 0.01000, 2.00),
("正等效方向上升至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向上升至 0.03 rad/s", 0.20000, 0.03000, 2.00),
("正等效方向上升至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向上升至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向上升至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向上升至 0.07 rad/s", 0.20000, 0.07000, 2.00),
("正等效方向上升至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向上升至 0.09 rad/s", 0.20000, 0.09000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.11 rad/s", 0.20000, 0.11000, 2.00),
("正等效方向上升至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向上升至 0.13 rad/s", 0.20000, 0.13000, 2.00),
("正等效方向上升至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向上升至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向上升至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向上升至 0.17 rad/s", 0.20000, 0.17000, 2.00),
("正等效方向上升至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向上升至 0.19 rad/s", 0.20000, 0.19000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.19 rad/s", 0.20000, 0.19000, 2.00),
("正等效方向下降至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向下降至 0.17 rad/s", 0.20000, 0.17000, 2.00),
("正等效方向下降至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向下降至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向下降至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向下降至 0.13 rad/s", 0.20000, 0.13000, 2.00),
("正等效方向下降至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向下降至 0.11 rad/s", 0.20000, 0.11000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.09 rad/s", 0.20000, 0.09000, 2.00),
("正等效方向下降至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向下降至 0.07 rad/s", 0.20000, 0.07000, 2.00),
("正等效方向下降至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向下降至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向下降至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向下降至 0.03 rad/s", 0.20000, 0.03000, 2.00),
("正等效方向下降至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向下降至 0.01 rad/s", 0.20000, 0.01000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.01 rad/s", 0.20000, -0.01000, 2.00),
("负等效方向上升至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向上升至 -0.03 rad/s", 0.20000, -0.03000, 2.00),
("负等效方向上升至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向上升至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向上升至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向上升至 -0.07 rad/s", 0.20000, -0.07000, 2.00),
("负等效方向上升至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向上升至 -0.09 rad/s", 0.20000, -0.09000, 2.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.11 rad/s", 0.20000, -0.11000, 2.00),
("负等效方向上升至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向上升至 -0.13 rad/s", 0.20000, -0.13000, 2.00),
("负等效方向上升至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向上升至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向上升至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向上升至 -0.17 rad/s", 0.20000, -0.17000, 2.00),
("负等效方向上升至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向上升至 -0.19 rad/s", 0.20000, -0.19000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.19 rad/s", 0.20000, -0.19000, 2.00),
("负等效方向下降至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向下降至 -0.17 rad/s", 0.20000, -0.17000, 2.00),
("负等效方向下降至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向下降至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向下降至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向下降至 -0.13 rad/s", 0.20000, -0.13000, 2.00),
("负等效方向下降至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向下降至 -0.11 rad/s", 0.20000, -0.11000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.09 rad/s", 0.20000, -0.09000, 2.00),
("负等效方向下降至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向下降至 -0.07 rad/s", 0.20000, -0.07000, 2.00),
("负等效方向下降至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向下降至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向下降至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向下降至 -0.03 rad/s", 0.20000, -0.03000, 2.00),
("负等效方向下降至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向下降至 -0.01 rad/s", 0.20000, -0.01000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.01 rad/s 的正向连续阶梯。

正向第 3 次:FWD_CONT_ANGULAR_STEP_G01_DW001_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G01_DW001/forward/FWD_CONT_ANGULAR_STEP_G01_DW001_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g01_dw001_fwd_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.01 rad/s", 0.20000, 0.01000, 2.00),
("正等效方向上升至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向上升至 0.03 rad/s", 0.20000, 0.03000, 2.00),
("正等效方向上升至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向上升至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向上升至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向上升至 0.07 rad/s", 0.20000, 0.07000, 2.00),
("正等效方向上升至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向上升至 0.09 rad/s", 0.20000, 0.09000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.11 rad/s", 0.20000, 0.11000, 2.00),
("正等效方向上升至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向上升至 0.13 rad/s", 0.20000, 0.13000, 2.00),
("正等效方向上升至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向上升至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向上升至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向上升至 0.17 rad/s", 0.20000, 0.17000, 2.00),
("正等效方向上升至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向上升至 0.19 rad/s", 0.20000, 0.19000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.19 rad/s", 0.20000, 0.19000, 2.00),
("正等效方向下降至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向下降至 0.17 rad/s", 0.20000, 0.17000, 2.00),
("正等效方向下降至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向下降至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向下降至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向下降至 0.13 rad/s", 0.20000, 0.13000, 2.00),
("正等效方向下降至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向下降至 0.11 rad/s", 0.20000, 0.11000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.09 rad/s", 0.20000, 0.09000, 2.00),
("正等效方向下降至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向下降至 0.07 rad/s", 0.20000, 0.07000, 2.00),
("正等效方向下降至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向下降至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向下降至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向下降至 0.03 rad/s", 0.20000, 0.03000, 2.00),
("正等效方向下降至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向下降至 0.01 rad/s", 0.20000, 0.01000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.01 rad/s", 0.20000, -0.01000, 2.00),
("负等效方向上升至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向上升至 -0.03 rad/s", 0.20000, -0.03000, 2.00),
("负等效方向上升至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向上升至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向上升至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向上升至 -0.07 rad/s", 0.20000, -0.07000, 2.00),
("负等效方向上升至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向上升至 -0.09 rad/s", 0.20000, -0.09000, 2.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.11 rad/s", 0.20000, -0.11000, 2.00),
("负等效方向上升至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向上升至 -0.13 rad/s", 0.20000, -0.13000, 2.00),
("负等效方向上升至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向上升至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向上升至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向上升至 -0.17 rad/s", 0.20000, -0.17000, 2.00),
("负等效方向上升至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向上升至 -0.19 rad/s", 0.20000, -0.19000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.19 rad/s", 0.20000, -0.19000, 2.00),
("负等效方向下降至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向下降至 -0.17 rad/s", 0.20000, -0.17000, 2.00),
("负等效方向下降至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向下降至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向下降至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向下降至 -0.13 rad/s", 0.20000, -0.13000, 2.00),
("负等效方向下降至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向下降至 -0.11 rad/s", 0.20000, -0.11000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.09 rad/s", 0.20000, -0.09000, 2.00),
("负等效方向下降至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向下降至 -0.07 rad/s", 0.20000, -0.07000, 2.00),
("负等效方向下降至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向下降至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向下降至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向下降至 -0.03 rad/s", 0.20000, -0.03000, 2.00),
("负等效方向下降至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向下降至 -0.01 rad/s", 0.20000, -0.01000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.01 rad/s 的正向连续阶梯。

反向第 1 次:REV_CONT_ANGULAR_STEP_G01_DW001_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G01_DW001/reverse/REV_CONT_ANGULAR_STEP_G01_DW001_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g01_dw001_rev_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.01 rad/s", -0.20000, -0.01000, 2.00),
("正等效方向上升至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向上升至 0.03 rad/s", -0.20000, -0.03000, 2.00),
("正等效方向上升至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向上升至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向上升至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向上升至 0.07 rad/s", -0.20000, -0.07000, 2.00),
("正等效方向上升至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向上升至 0.09 rad/s", -0.20000, -0.09000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.11 rad/s", -0.20000, -0.11000, 2.00),
("正等效方向上升至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向上升至 0.13 rad/s", -0.20000, -0.13000, 2.00),
("正等效方向上升至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向上升至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向上升至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向上升至 0.17 rad/s", -0.20000, -0.17000, 2.00),
("正等效方向上升至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向上升至 0.19 rad/s", -0.20000, -0.19000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.19 rad/s", -0.20000, -0.19000, 2.00),
("正等效方向下降至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向下降至 0.17 rad/s", -0.20000, -0.17000, 2.00),
("正等效方向下降至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向下降至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向下降至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向下降至 0.13 rad/s", -0.20000, -0.13000, 2.00),
("正等效方向下降至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向下降至 0.11 rad/s", -0.20000, -0.11000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.09 rad/s", -0.20000, -0.09000, 2.00),
("正等效方向下降至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向下降至 0.07 rad/s", -0.20000, -0.07000, 2.00),
("正等效方向下降至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向下降至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向下降至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向下降至 0.03 rad/s", -0.20000, -0.03000, 2.00),
("正等效方向下降至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向下降至 0.01 rad/s", -0.20000, -0.01000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.01 rad/s", -0.20000, 0.01000, 2.00),
("负等效方向上升至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向上升至 -0.03 rad/s", -0.20000, 0.03000, 2.00),
("负等效方向上升至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向上升至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向上升至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向上升至 -0.07 rad/s", -0.20000, 0.07000, 2.00),
("负等效方向上升至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向上升至 -0.09 rad/s", -0.20000, 0.09000, 2.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.11 rad/s", -0.20000, 0.11000, 2.00),
("负等效方向上升至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向上升至 -0.13 rad/s", -0.20000, 0.13000, 2.00),
("负等效方向上升至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向上升至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向上升至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向上升至 -0.17 rad/s", -0.20000, 0.17000, 2.00),
("负等效方向上升至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向上升至 -0.19 rad/s", -0.20000, 0.19000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.19 rad/s", -0.20000, 0.19000, 2.00),
("负等效方向下降至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向下降至 -0.17 rad/s", -0.20000, 0.17000, 2.00),
("负等效方向下降至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向下降至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向下降至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向下降至 -0.13 rad/s", -0.20000, 0.13000, 2.00),
("负等效方向下降至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向下降至 -0.11 rad/s", -0.20000, 0.11000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.09 rad/s", -0.20000, 0.09000, 2.00),
("负等效方向下降至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向下降至 -0.07 rad/s", -0.20000, 0.07000, 2.00),
("负等效方向下降至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向下降至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向下降至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向下降至 -0.03 rad/s", -0.20000, 0.03000, 2.00),
("负等效方向下降至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向下降至 -0.01 rad/s", -0.20000, 0.01000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

反向第 2 次:REV_CONT_ANGULAR_STEP_G01_DW001_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G01_DW001/reverse/REV_CONT_ANGULAR_STEP_G01_DW001_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g01_dw001_rev_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.01 rad/s", -0.20000, -0.01000, 2.00),
("正等效方向上升至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向上升至 0.03 rad/s", -0.20000, -0.03000, 2.00),
("正等效方向上升至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向上升至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向上升至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向上升至 0.07 rad/s", -0.20000, -0.07000, 2.00),
("正等效方向上升至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向上升至 0.09 rad/s", -0.20000, -0.09000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.11 rad/s", -0.20000, -0.11000, 2.00),
("正等效方向上升至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向上升至 0.13 rad/s", -0.20000, -0.13000, 2.00),
("正等效方向上升至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向上升至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向上升至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向上升至 0.17 rad/s", -0.20000, -0.17000, 2.00),
("正等效方向上升至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向上升至 0.19 rad/s", -0.20000, -0.19000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.19 rad/s", -0.20000, -0.19000, 2.00),
("正等效方向下降至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向下降至 0.17 rad/s", -0.20000, -0.17000, 2.00),
("正等效方向下降至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向下降至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向下降至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向下降至 0.13 rad/s", -0.20000, -0.13000, 2.00),
("正等效方向下降至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向下降至 0.11 rad/s", -0.20000, -0.11000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.09 rad/s", -0.20000, -0.09000, 2.00),
("正等效方向下降至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向下降至 0.07 rad/s", -0.20000, -0.07000, 2.00),
("正等效方向下降至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向下降至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向下降至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向下降至 0.03 rad/s", -0.20000, -0.03000, 2.00),
("正等效方向下降至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向下降至 0.01 rad/s", -0.20000, -0.01000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.01 rad/s", -0.20000, 0.01000, 2.00),
("负等效方向上升至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向上升至 -0.03 rad/s", -0.20000, 0.03000, 2.00),
("负等效方向上升至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向上升至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向上升至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向上升至 -0.07 rad/s", -0.20000, 0.07000, 2.00),
("负等效方向上升至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向上升至 -0.09 rad/s", -0.20000, 0.09000, 2.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.11 rad/s", -0.20000, 0.11000, 2.00),
("负等效方向上升至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向上升至 -0.13 rad/s", -0.20000, 0.13000, 2.00),
("负等效方向上升至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向上升至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向上升至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向上升至 -0.17 rad/s", -0.20000, 0.17000, 2.00),
("负等效方向上升至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向上升至 -0.19 rad/s", -0.20000, 0.19000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.19 rad/s", -0.20000, 0.19000, 2.00),
("负等效方向下降至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向下降至 -0.17 rad/s", -0.20000, 0.17000, 2.00),
("负等效方向下降至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向下降至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向下降至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向下降至 -0.13 rad/s", -0.20000, 0.13000, 2.00),
("负等效方向下降至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向下降至 -0.11 rad/s", -0.20000, 0.11000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.09 rad/s", -0.20000, 0.09000, 2.00),
("负等效方向下降至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向下降至 -0.07 rad/s", -0.20000, 0.07000, 2.00),
("负等效方向下降至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向下降至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向下降至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向下降至 -0.03 rad/s", -0.20000, 0.03000, 2.00),
("负等效方向下降至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向下降至 -0.01 rad/s", -0.20000, 0.01000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

反向第 3 次:REV_CONT_ANGULAR_STEP_G01_DW001_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G01_DW001/reverse/REV_CONT_ANGULAR_STEP_G01_DW001_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g01_dw001_rev_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.01 rad/s", -0.20000, -0.01000, 2.00),
("正等效方向上升至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向上升至 0.03 rad/s", -0.20000, -0.03000, 2.00),
("正等效方向上升至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向上升至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向上升至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向上升至 0.07 rad/s", -0.20000, -0.07000, 2.00),
("正等效方向上升至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向上升至 0.09 rad/s", -0.20000, -0.09000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.11 rad/s", -0.20000, -0.11000, 2.00),
("正等效方向上升至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向上升至 0.13 rad/s", -0.20000, -0.13000, 2.00),
("正等效方向上升至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向上升至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向上升至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向上升至 0.17 rad/s", -0.20000, -0.17000, 2.00),
("正等效方向上升至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向上升至 0.19 rad/s", -0.20000, -0.19000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.19 rad/s", -0.20000, -0.19000, 2.00),
("正等效方向下降至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向下降至 0.17 rad/s", -0.20000, -0.17000, 2.00),
("正等效方向下降至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向下降至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向下降至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向下降至 0.13 rad/s", -0.20000, -0.13000, 2.00),
("正等效方向下降至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向下降至 0.11 rad/s", -0.20000, -0.11000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.09 rad/s", -0.20000, -0.09000, 2.00),
("正等效方向下降至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向下降至 0.07 rad/s", -0.20000, -0.07000, 2.00),
("正等效方向下降至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向下降至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向下降至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向下降至 0.03 rad/s", -0.20000, -0.03000, 2.00),
("正等效方向下降至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向下降至 0.01 rad/s", -0.20000, -0.01000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.01 rad/s", -0.20000, 0.01000, 2.00),
("负等效方向上升至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向上升至 -0.03 rad/s", -0.20000, 0.03000, 2.00),
("负等效方向上升至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向上升至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向上升至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向上升至 -0.07 rad/s", -0.20000, 0.07000, 2.00),
("负等效方向上升至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向上升至 -0.09 rad/s", -0.20000, 0.09000, 2.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.11 rad/s", -0.20000, 0.11000, 2.00),
("负等效方向上升至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向上升至 -0.13 rad/s", -0.20000, 0.13000, 2.00),
("负等效方向上升至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向上升至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向上升至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向上升至 -0.17 rad/s", -0.20000, 0.17000, 2.00),
("负等效方向上升至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向上升至 -0.19 rad/s", -0.20000, 0.19000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.19 rad/s", -0.20000, 0.19000, 2.00),
("负等效方向下降至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向下降至 -0.17 rad/s", -0.20000, 0.17000, 2.00),
("负等效方向下降至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向下降至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向下降至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向下降至 -0.13 rad/s", -0.20000, 0.13000, 2.00),
("负等效方向下降至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向下降至 -0.11 rad/s", -0.20000, 0.11000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.09 rad/s", -0.20000, 0.09000, 2.00),
("负等效方向下降至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向下降至 -0.07 rad/s", -0.20000, 0.07000, 2.00),
("负等效方向下降至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向下降至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向下降至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向下降至 -0.03 rad/s", -0.20000, 0.03000, 2.00),
("负等效方向下降至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向下降至 -0.01 rad/s", -0.20000, 0.01000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

8.6.3 连续增量 Δω=0.02 rad/s

正向上升等级:0.00, 0.02, 0.04, 0.06, 0.08, 0.10, 0.12, 0.14, 0.16, 0.18, 0.20 rad/s

随后按相同等级降回 0,保持运动直线 3 秒,再对负角速度执行同样的连续升降序列。

正向第 1 次:FWD_CONT_ANGULAR_STEP_G02_DW002_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G02_DW002/forward/FWD_CONT_ANGULAR_STEP_G02_DW002_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g02_dw002_fwd_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向上升至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向上升至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向上升至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向上升至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向上升至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向上升至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向下降至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向下降至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向下降至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向下降至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向下降至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向下降至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向上升至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向上升至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向上升至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向上升至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向上升至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向上升至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向下降至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向下降至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向下降至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向下降至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向下降至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向下降至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.02 rad/s 的正向连续阶梯。

正向第 2 次:FWD_CONT_ANGULAR_STEP_G02_DW002_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G02_DW002/forward/FWD_CONT_ANGULAR_STEP_G02_DW002_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g02_dw002_fwd_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向上升至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向上升至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向上升至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向上升至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向上升至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向上升至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向下降至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向下降至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向下降至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向下降至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向下降至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向下降至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向上升至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向上升至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向上升至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向上升至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向上升至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向上升至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向下降至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向下降至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向下降至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向下降至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向下降至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向下降至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.02 rad/s 的正向连续阶梯。

正向第 3 次:FWD_CONT_ANGULAR_STEP_G02_DW002_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G02_DW002/forward/FWD_CONT_ANGULAR_STEP_G02_DW002_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g02_dw002_fwd_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向上升至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向上升至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向上升至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向上升至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向上升至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向上升至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.18 rad/s", 0.20000, 0.18000, 2.00),
("正等效方向下降至 0.16 rad/s", 0.20000, 0.16000, 2.00),
("正等效方向下降至 0.14 rad/s", 0.20000, 0.14000, 2.00),
("正等效方向下降至 0.12 rad/s", 0.20000, 0.12000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.08 rad/s", 0.20000, 0.08000, 2.00),
("正等效方向下降至 0.06 rad/s", 0.20000, 0.06000, 2.00),
("正等效方向下降至 0.04 rad/s", 0.20000, 0.04000, 2.00),
("正等效方向下降至 0.02 rad/s", 0.20000, 0.02000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向上升至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向上升至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向上升至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向上升至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向上升至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向上升至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.18 rad/s", 0.20000, -0.18000, 2.00),
("负等效方向下降至 -0.16 rad/s", 0.20000, -0.16000, 2.00),
("负等效方向下降至 -0.14 rad/s", 0.20000, -0.14000, 2.00),
("负等效方向下降至 -0.12 rad/s", 0.20000, -0.12000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.08 rad/s", 0.20000, -0.08000, 2.00),
("负等效方向下降至 -0.06 rad/s", 0.20000, -0.06000, 2.00),
("负等效方向下降至 -0.04 rad/s", 0.20000, -0.04000, 2.00),
("负等效方向下降至 -0.02 rad/s", 0.20000, -0.02000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.02 rad/s 的正向连续阶梯。

反向第 1 次:REV_CONT_ANGULAR_STEP_G02_DW002_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G02_DW002/reverse/REV_CONT_ANGULAR_STEP_G02_DW002_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g02_dw002_rev_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向上升至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向上升至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向上升至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向上升至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向上升至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向上升至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向下降至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向下降至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向下降至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向下降至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向下降至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向下降至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向上升至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向上升至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向上升至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向上升至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向上升至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向上升至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向下降至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向下降至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向下降至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向下降至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向下降至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向下降至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

反向第 2 次:REV_CONT_ANGULAR_STEP_G02_DW002_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G02_DW002/reverse/REV_CONT_ANGULAR_STEP_G02_DW002_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g02_dw002_rev_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向上升至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向上升至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向上升至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向上升至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向上升至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向上升至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向下降至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向下降至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向下降至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向下降至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向下降至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向下降至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向上升至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向上升至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向上升至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向上升至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向上升至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向上升至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向下降至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向下降至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向下降至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向下降至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向下降至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向下降至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

反向第 3 次:REV_CONT_ANGULAR_STEP_G02_DW002_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G02_DW002/reverse/REV_CONT_ANGULAR_STEP_G02_DW002_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g02_dw002_rev_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向上升至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向上升至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向上升至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向上升至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向上升至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向上升至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.18 rad/s", -0.20000, -0.18000, 2.00),
("正等效方向下降至 0.16 rad/s", -0.20000, -0.16000, 2.00),
("正等效方向下降至 0.14 rad/s", -0.20000, -0.14000, 2.00),
("正等效方向下降至 0.12 rad/s", -0.20000, -0.12000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.08 rad/s", -0.20000, -0.08000, 2.00),
("正等效方向下降至 0.06 rad/s", -0.20000, -0.06000, 2.00),
("正等效方向下降至 0.04 rad/s", -0.20000, -0.04000, 2.00),
("正等效方向下降至 0.02 rad/s", -0.20000, -0.02000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向上升至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向上升至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向上升至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向上升至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向上升至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向上升至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.18 rad/s", -0.20000, 0.18000, 2.00),
("负等效方向下降至 -0.16 rad/s", -0.20000, 0.16000, 2.00),
("负等效方向下降至 -0.14 rad/s", -0.20000, 0.14000, 2.00),
("负等效方向下降至 -0.12 rad/s", -0.20000, 0.12000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.08 rad/s", -0.20000, 0.08000, 2.00),
("负等效方向下降至 -0.06 rad/s", -0.20000, 0.06000, 2.00),
("负等效方向下降至 -0.04 rad/s", -0.20000, 0.04000, 2.00),
("负等效方向下降至 -0.02 rad/s", -0.20000, 0.02000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

8.6.4 连续增量 Δω=0.05 rad/s

正向上升等级:0.00, 0.05, 0.10, 0.15, 0.20 rad/s

随后按相同等级降回 0,保持运动直线 3 秒,再对负角速度执行同样的连续升降序列。

正向第 1 次:FWD_CONT_ANGULAR_STEP_G05_DW005_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G05_DW005/forward/FWD_CONT_ANGULAR_STEP_G05_DW005_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g05_dw005_fwd_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.05 rad/s 的正向连续阶梯。

正向第 2 次:FWD_CONT_ANGULAR_STEP_G05_DW005_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G05_DW005/forward/FWD_CONT_ANGULAR_STEP_G05_DW005_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g05_dw005_fwd_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.05 rad/s 的正向连续阶梯。

正向第 3 次:FWD_CONT_ANGULAR_STEP_G05_DW005_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G05_DW005/forward/FWD_CONT_ANGULAR_STEP_G05_DW005_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g05_dw005_fwd_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.15 rad/s", 0.20000, 0.15000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.05 rad/s", 0.20000, 0.05000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.15 rad/s", 0.20000, -0.15000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.05 rad/s", 0.20000, -0.05000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.05 rad/s 的正向连续阶梯。

反向第 1 次:REV_CONT_ANGULAR_STEP_G05_DW005_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G05_DW005/reverse/REV_CONT_ANGULAR_STEP_G05_DW005_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g05_dw005_rev_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

反向第 2 次:REV_CONT_ANGULAR_STEP_G05_DW005_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G05_DW005/reverse/REV_CONT_ANGULAR_STEP_G05_DW005_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g05_dw005_rev_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

反向第 3 次:REV_CONT_ANGULAR_STEP_G05_DW005_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G05_DW005/reverse/REV_CONT_ANGULAR_STEP_G05_DW005_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g05_dw005_rev_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.15 rad/s", -0.20000, -0.15000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.05 rad/s", -0.20000, -0.05000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.15 rad/s", -0.20000, 0.15000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.05 rad/s", -0.20000, 0.05000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

8.6.5 连续增量 Δω=0.10 rad/s

正向上升等级:0.00, 0.10, 0.20 rad/s

随后按相同等级降回 0,保持运动直线 3 秒,再对负角速度执行同样的连续升降序列。

正向第 1 次:FWD_CONT_ANGULAR_STEP_G10_DW010_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G10_DW010/forward/FWD_CONT_ANGULAR_STEP_G10_DW010_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g10_dw010_fwd_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.10 rad/s 的正向连续阶梯。

正向第 2 次:FWD_CONT_ANGULAR_STEP_G10_DW010_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G10_DW010/forward/FWD_CONT_ANGULAR_STEP_G10_DW010_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g10_dw010_fwd_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.10 rad/s 的正向连续阶梯。

正向第 3 次:FWD_CONT_ANGULAR_STEP_G10_DW010_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G10_DW010/forward/FWD_CONT_ANGULAR_STEP_G10_DW010_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g10_dw010_fwd_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.10 rad/s", 0.20000, 0.10000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.10 rad/s", 0.20000, -0.10000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.10 rad/s 的正向连续阶梯。

反向第 1 次:REV_CONT_ANGULAR_STEP_G10_DW010_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G10_DW010/reverse/REV_CONT_ANGULAR_STEP_G10_DW010_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g10_dw010_rev_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

反向第 2 次:REV_CONT_ANGULAR_STEP_G10_DW010_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G10_DW010/reverse/REV_CONT_ANGULAR_STEP_G10_DW010_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g10_dw010_rev_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

反向第 3 次:REV_CONT_ANGULAR_STEP_G10_DW010_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G10_DW010/reverse/REV_CONT_ANGULAR_STEP_G10_DW010_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g10_dw010_rev_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.10 rad/s", -0.20000, -0.10000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.10 rad/s", -0.20000, 0.10000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

8.6.6 连续增量 Δω=0.20 rad/s

正向上升等级:0.00, 0.20 rad/s

随后按相同等级降回 0,保持运动直线 3 秒,再对负角速度执行同样的连续升降序列。

正向第 1 次:FWD_CONT_ANGULAR_STEP_G20_DW020_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G20_DW020/forward/FWD_CONT_ANGULAR_STEP_G20_DW020_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g20_dw020_fwd_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.20 rad/s 的正向连续阶梯。

正向第 2 次:FWD_CONT_ANGULAR_STEP_G20_DW020_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G20_DW020/forward/FWD_CONT_ANGULAR_STEP_G20_DW020_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g20_dw020_fwd_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.20 rad/s 的正向连续阶梯。

正向第 3 次:FWD_CONT_ANGULAR_STEP_G20_DW020_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G20_DW020/forward/FWD_CONT_ANGULAR_STEP_G20_DW020_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g20_dw020_fwd_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("正等效方向上升至 0.20 rad/s", 0.20000, 0.20000, 2.00),
("正等效方向下降至 0.00 rad/s", 0.20000, 0.00000, 2.00),
("保持运动直线回正", 0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.20 rad/s", 0.20000, -0.20000, 2.00),
("负等效方向下降至 -0.00 rad/s", 0.20000, -0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

本 bag 只对应 Δω=0.20 rad/s 的正向连续阶梯。

反向第 1 次:REV_CONT_ANGULAR_STEP_G20_DW020_V020_run01

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G20_DW020/reverse/REV_CONT_ANGULAR_STEP_G20_DW020_V020_run01 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g20_dw020_rev_run01")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

反向第 2 次:REV_CONT_ANGULAR_STEP_G20_DW020_V020_run02

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G20_DW020/reverse/REV_CONT_ANGULAR_STEP_G20_DW020_V020_run02 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g20_dw020_rev_run02")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

反向第 3 次:REV_CONT_ANGULAR_STEP_G20_DW020_V020_run03

终端 1:开始录制本次测试对应的 bag

bash 复制代码
ros2 bag record \
  -o ~/ranger_motion_test/08_completion/continuous_angular_step/G20_DW020/reverse/REV_CONT_ANGULAR_STEP_G20_DW020_V020_run03 \
  /cmd_vel /odom /lio/odom /tf /tf_static

终端 2:一次性复制并执行下面的完整命令块

bash 复制代码
python3 - <<'PY'
import time

import rclpy
from geometry_msgs.msg import Twist

rclpy.init()
node = rclpy.create_node("ranger_cont_angular_g20_dw020_rev_run03")
publisher = node.create_publisher(Twist, "/cmd_vel", 10)

schedule = [
    ("静止预记录", 0.00000, 0.00000, 5.00),
("正等效方向上升至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("正等效方向上升至 0.20 rad/s", -0.20000, -0.20000, 2.00),
("正等效方向下降至 0.00 rad/s", -0.20000, -0.00000, 2.00),
("保持运动直线回正", -0.20000, 0.00000, 3.00),
("负等效方向上升至 -0.20 rad/s", -0.20000, 0.20000, 2.00),
("负等效方向下降至 -0.00 rad/s", -0.20000, 0.00000, 2.00),
("主动零速度停车", 0.00000, 0.00000, 5.00),
]

rate_hz = 20.0
period = 1.0 / rate_hz
message = Twist()

# 等待 DDS 完成发现;之后始终由同一个发布节点连续发送。
time.sleep(1.0)

try:
    for index, (label, linear_x, angular_z, hold_time) in enumerate(
        schedule, start=1
    ):
        print(
            f"[{index:02d}/{len(schedule):02d}] "
            f"{label}: linear.x={linear_x:.3f}, "
            f"angular.z={angular_z:.3f}, hold={hold_time:.2f} s",
            flush=True,
        )

        message.linear.x = float(linear_x)
        message.linear.y = 0.0
        message.linear.z = 0.0
        message.angular.x = 0.0
        message.angular.y = 0.0
        message.angular.z = float(angular_z)

        end_time = time.monotonic() + float(hold_time)
        next_publish = time.monotonic()

        while rclpy.ok() and time.monotonic() < end_time:
            publisher.publish(message)
            rclpy.spin_once(node, timeout_sec=0.0)

            next_publish += period
            sleep_time = next_publish - time.monotonic()
            if sleep_time > 0.0:
                time.sleep(sleep_time)
            else:
                next_publish = time.monotonic()

except KeyboardInterrupt:
    print("收到中断,立即连续发布零速度。", flush=True)

finally:
    message.linear.x = 0.0
    message.angular.z = 0.0

    # 即使人工中断,也连续发布约 1 s 零速度后再退出。
    for _ in range(20):
        publisher.publish(message)
        rclpy.spin_once(node, timeout_sec=0.0)
        time.sleep(period)

    node.destroy_node()
    if rclpy.ok():
        rclpy.shutdown()
PY

终端 1:终端 2 完成最终零速度阶段、车辆完全停止后,按 Ctrl+C 停止录包。

反向时线速度和角速度符号已按相同等效转向关系进行处理。

8.7 第八部分完成后可补齐的分析指标

8.7.1 主动停车

  • 零速度指令首帧到实际速度下降至 10% 的时间;
  • 零速度指令首帧到车辆完全停止的时间;
  • 主动零速度后的制动距离;
  • 主动停车与第四部分命令断流停车的时间和距离差;
  • 正向、反向以及不同速度下的制动对称性。

8.7.2 连续转向切换

  • ω=±0.2 → 0 的真实回正延迟;
  • ω=+0.2 → -0.2 或反向切换的过零时间;
  • 横摆角速度超调;
  • 回正阶段残余角速度;
  • 正向、反向和左右转向对称性。

8.7.3 连续角速度增量

  • 不同 Δω 下的响应延迟、上升时间和稳态误差;
  • 小增量是否存在死区;
  • 增量大小与超调量之间的关系;
  • 升阶与降阶是否存在滞回;
  • 正角速度与负角速度是否对称;
  • 正向与反向是否对称;
  • 能够兼顾快速响应、低误差和低超调的推荐角速度增量。

8.8 测试结束后的数量检查

第七部分应生成 24 个 bag:

bash 复制代码
find ~/ranger_motion_test/07_continuous_variable -name metadata.yaml | wc -l

预期输出:

text 复制代码
24

第八部分应生成 54 个 bag:

bash 复制代码
find ~/ranger_motion_test/08_completion -name metadata.yaml | wc -l

预期输出:

text 复制代码
54

全部八部分累计应有 157 个 bag:

bash 复制代码
find ~/ranger_motion_test -name metadata.yaml | wc -l

预期输出:

text 复制代码
157

所有新增测试均使用新的目录和 bag 名称,不会覆盖前六部分已经录制的 79 个 bag。

相关推荐
某林21210 小时前
大模型边缘部署到底层硬件闭环
python·架构·机器人·硬件架构·ros2
某林2122 天前
构建高精度 6-DoF 灵巧手控制系统
人工智能·3d·机器人·ros2·技术复盘
zh路西法3 天前
【10天速通Navigation2】(九):LQR最优控制器的原理推导与Nav2插件实现
c++·ros2·最优控制·lqr·navigation2
视图猿人4 天前
ROS2 DDS 基础配置速查表及高级参数调优总结
ros2
视图猿人4 天前
ROS2中配置高带宽图像传输零拷贝文件
ros2
再遇当年6 天前
Ubuntu 22.04 + ROS 2 Humble 项目通过 TRAE 改成 Ubuntu 202.04 + ROS 1 Noetic 项目的SKLL技能
linux·运维·ubuntu·ros2·ros1·skll·sill
zh路西法8 天前
【ICP点云配准】从数学原理到手写C++——SVD求解与多分辨率优化
开发语言·c++·模拟退火算法·svd·ros2·icp·点云匹配
神经智研社9 天前
ROS2--8章 机器人坐标系 tf2库
机器人·ros2·机器人环境搭建·ros2 jazzy·机器人坐标系系统
某林2129 天前
从硬件解耦、3D公差设计到ROS 2柔性导航
人工智能·3d·机器人·ros2·技术复盘