Files
FlyShotHost/docs/2026042802-1-trigger-vs-teach-point-analysis.md
yunxiao.zhu f7e2bb0e7b feat(*): 添加触发样本偏移与实发轨迹分析导出
* 为 RobotConfig 增加 trigger_sample_index_offset_cycles 配置
  * 让 DO 事件携带示教点关节角并按最接近 sample 绑定触发
  * 调整运行时 IO 地址位掩码映射并补充 ShotEvents 导出
  * 新增 2026042802-1 抓包分析脚本、数据产物与结论文档
  * 补齐配置兼容、规划绑定和运行时触发相关测试
2026-05-09 11:12:31 +08:00

32 KiB
Raw Blame History

2026042802-1 抓包触发点与示教点对比记录

1. 目的

  • 使用 ../Rvbust/uttc-20260428/2026042802-1.pcap 确认当前仓库是否已经导出过这份抓包对应的真实运动轨迹发包记录。
  • 如果现有导出不包含 IO 触发信息,则补一份“真实 J519 发包 + IO 标记”导出。
  • 将抓包里真实触发 IO 的关节坐标,与 Config/RobotConfig.jsonflying_shots.UTTC_MS11 的示教点逐点对比。
  • 把结论固定成仓库文档,后续继续和新程序抓包、ShotEvents.json、运行时导出结果做同口径比较。

2. 数据来源与口径

  • 抓包文件:../Rvbust/uttc-20260428/2026042802-1.pcap
  • 旧目录配置文件:../Rvbust/uttc-20260428/RobotConfig.json
  • 新版程序运行时配置文件:Config/RobotConfig.json
  • 对比对象:两份配置中的 flying_shots.UTTC_MS11
  • 协议口径:只看 192.168.10.10 -> 192.168.10.11UDP 60015 64B J519 命令帧
  • 触发判定:write_io_value > 0 视为 IO 置位
  • 去重规则:由于 io_keep_cycles=2,每次触发只记录首个高电平帧
  • 关节单位:抓包 J519 目标关节为 degRobotConfig.json traj_waypointsrad,分析时先转成 deg

补充说明:

  • ../Rvbust/uttc-20260428/ 目录中的内容代表旧抓包及其同目录资料。
  • Config/Data/UTTC_MS11/ 目录中的内容代表现在新版程序的运行时导出。
  • 本文不会把两者混成同一次运行的数据;这里只拿新版程序目录里的文件做“现状参照”,核心分析对象仍然是旧抓包 2026042802-1.pcap

3. 现状核对

3.1 仓库里已经存在什么

  • 旧抓包目录已有原始抓包:../Rvbust/uttc-20260428/2026042802-1.pcap
  • 旧抓包目录已有按运动段拆分的导出:../Rvbust/uttc-20260428/2026042802-1_joint_segments/
  • 旧抓包目录已有速度档位整理结果:../Rvbust/uttc-20260428/1倍速度 角度坐标点/真实轨迹JointDetialTraj.txt
  • 新版程序目录已有运行时导出:Config/Data/UTTC_MS11/ActualSendJointTraj.txt
  • 新版程序目录已有运行时导出:Config/Data/UTTC_MS11/ActualSendTiming.txt
  • 新版程序目录已有理论触发时间轴:Config/Data/UTTC_MS11/ShotEvents.json

3.2 当前缺的是什么

  • 新版程序目录中的 ActualSendJointTraj.txt 是现在新版程序落盘的真实发送关节轨迹,但不带 IO 列,也不属于 2026042802-1.pcap 这次旧抓包本身。
  • 旧抓包目录中的 2026042802-1_joint_segments/ 是按运动段拆开的 JointDetialTraj.txt 风格导出,也不包含整条抓包的 IO 标记。
  • 旧抓包目录中的 1倍速度 角度坐标点/真实轨迹JointDetialTraj.txt 提供了真实轨迹文本,但同样不带 IO 触发列。
  • 本次分析前,没有发现一份专门对应 2026042802-1.pcap 的“整条真实 J519 发包记录 + IO 标记 + 触发帧筛选”组合导出。

结论: 旧抓包目录和新版程序目录里都已经有各自的轨迹资料,但在本次分析前,还没有一份专门对应 2026042802-1.pcap 的、带 IO 触发信息的整条实际发包记录。

4. 本次新增导出

本次补充生成了以下文件:

  • analysis/2026042802-1/2026042802-1_j519_actual_send_all_with_io.csv
    • 1788 条客户端真实下发 J519 命令
    • 包含 frame_number / time_relative_s / sequence / write_io_value / io_addrs / j1..j6
  • analysis/2026042802-1/2026042802-1_j519_trigger_frames.csv
    • 只保留每次 IO 高电平脉冲的第一帧
    • 17 条,和 ShotEvents.json 的触发次数一致
  • analysis/2026042802-1/2026042802-1_trigger_vs_teach_points.csv
    • 每个真实触发帧与 UTTC_MS11 示教点的逐点偏差表
  • analysis/2026042802-1/2026042802-1_analysis_summary.json
    • 本文档引用的汇总统计

对应复现脚本:

  • analysis/analyze_2026042802_1_trigger_vs_teach_points.py

5. 触发与地址核对

  • 抓包提取到的真实触发次数:17
  • ShotEvents.json 中的触发次数:17
  • write_io_value 解码出的 io_addrsUTTC_MS11.addr 没有真实内容不一致
  • 其中有 14 处列表顺序不同,例如抓包里是 [2, 3, 4],配置里写的是 [3, 4, 2]
  • 14 处只是顺序差异,不是触发地址集合差异
  • 真正的地址集合不匹配数:0

结论: 2026042802-1.pcap 中真实发出的 IO 地址集合,与 UTTC_MS11 配置的 addr 集合是一致的。

6. 触发点与示教点误差统计

  • 平均单点最大单轴误差:4.241584 deg
  • 最大单轴误差:11.130744 deg
  • 平均 RMS 误差:2.540614 deg
  • 最大误差轴分布:J6=9J4=5J5=1J2=1J1=1

这说明:

  • 这份旧抓包里的真实触发点,并不是严格贴在配置示教点上触发
  • 误差主要集中在 J6J4
  • 由于 ../Rvbust/uttc-20260428/RobotConfig.json 和当前仓库 Config/RobotConfig.jsonUTTC_MS11 四组关键字段完全一致,因此这里的“示教点”对旧抓包和当前配置是同一组数据
  • 如果用户的“对焦准确示教点”就是这组 traj_waypoints,那么旧程序实际触发时已经存在几度量级的偏移,而不是亚角度级别的完全重合

7. 最大偏差点

按最大单轴误差从大到小列出前 5 个触发点:

触发序号 waypoint_index 实际 io_addrs 最大误差轴 最大误差(deg) RMS误差(deg) 主要差值
3 3 [2, 3, 4] J6 11.130744 5.955608 J4 +8.811537J6 -11.130744
7 7 [3, 4] J6 7.194107 4.618545 J2 -5.441249J4 -6.792278J6 +7.194107
17 18 [3, 4] J4 6.848522 4.036140 J2 +1.835940J4 -6.848522J6 +6.320424
13 13 [3, 4] J1 6.656209 4.109282 J1 +6.656209J3 -3.848692J4 +3.795032
8 8 [2, 4] J6 5.939884 3.533133 J2 -2.961069J4 -5.456660J6 +5.939884

8. 逐点结果

完整逐点数据已写入:

  • analysis/2026042802-1/2026042802-1_trigger_vs_teach_points.csv

这里保留简表,方便直接看每次触发的最大偏差:

触发序号 waypoint_index frame seq time(s) io_addrs 最大误差轴 最大误差(deg) RMS误差(deg)
1 1 1955 1381124 5.888271 [2, 4] J6 3.096909 2.341857
2 2 2151 1381198 6.480252 [2, 3, 4] J4 2.737488 1.612807
3 3 2223 1381226 6.704225 [2, 3, 4] J6 11.130744 5.955608
4 4 2336 1381269 7.048266 [2, 4] J6 2.373383 1.439206
5 5 2477 1381321 7.464289 [2, 4] J5 1.973330 1.273254
6 6 2665 1381391 8.024277 [3, 4] J2 1.265078 0.786940
7 7 2811 1381447 8.472317 [3, 4] J6 7.194107 4.618545
8 8 2908 1381483 8.760361 [2, 4] J6 5.939884 3.533133
9 9 3112 1381561 9.384332 [2, 4] J4 2.414749 1.565121
10 10 3170 1381583 9.560358 [2, 4] J4 3.629631 2.268783
11 11 3350 1381651 10.104322 [2, 4] J6 3.113037 2.115595
12 12 3470 1381697 10.472333 [2, 4] J6 4.018547 2.003201
13 13 3627 1381756 10.944371 [3, 4] J1 6.656209 4.109282
14 14 3710 1381787 11.192339 [2, 4] J6 2.834801 1.514764
15 15 3796 1381820 11.456361 [2, 4] J4 2.496544 1.640818
16 16 3870 1381848 11.680362 [2, 4] J6 3.782349 2.176883
17 18 4138 1381950 12.496365 [3, 4] J4 6.848522 4.036140

9. UTTC_MS11 示教点角度表

为了便于和导出的触发序列直接手工比对,下面把当前 UTTC_MS11 示教点从 rad 转成 deg

说明:

  • waypoint_index 使用配置里的原始下标。
  • shot_flag=true 表示该点在配置里是拍照点候选。
  • addr 保留原配置里的触发地址列表,便于和 io_addrs 对照。
waypoint_index shot_flag addr J1(deg) J2(deg) J3(deg) J4(deg) J5(deg) J6(deg)
0 false [] 60.546226 0.668344 -1.025155 -0.869105 1.231405 0.548197
1 true [2, 4] 48.886810 2.198985 -11.021017 0.410210 6.248381 2.294991
2 true [3, 4, 2] 55.347755 11.807040 -7.009095 -71.014331 6.012065 74.249532
3 true [3, 4, 2] 55.109808 8.759497 -8.518217 -41.117250 10.108488 41.956933
4 true [4, 2] 43.653593 -1.629660 -17.715754 5.995209 32.171718 -22.573973
5 true [4, 2] 64.582445 4.262979 -15.669217 -29.952927 29.850440 45.626527
6 true [3, 4] 60.479483 23.068782 -5.011264 36.106855 5.525681 -31.302564
7 true [3, 4] 70.475837 16.393849 -13.456948 -27.892319 14.535662 31.711933
8 true [4, 2] 69.582464 -17.105713 -8.710590 -58.475694 7.630590 64.437733
9 true [4, 2] 73.571259 -6.429845 -9.628580 -128.808647 14.671083 140.002048
10 true [4, 2] 75.569386 -14.679306 -7.294156 -130.923033 17.688361 141.893508
11 true [4, 2] 86.093498 -14.498333 -13.681511 -69.868296 26.742682 88.999414
12 true [4, 2] 61.720733 -4.232789 -9.784423 -108.384083 22.160772 118.142064
13 true [4, 3] 79.757083 4.640231 -15.311186 -56.035312 26.133138 52.152257
14 true [4, 2] 108.955551 1.370760 -33.594425 -48.367361 43.963404 85.039261
15 true [4, 2] 110.584848 -3.860900 -32.396407 -51.112591 44.229160 87.624000
16 true [4, 2] 118.095952 -17.376387 -31.069001 -59.560538 48.732576 94.134771
17 false [4, 2] 62.573787 -22.938069 -10.333288 77.491373 35.583412 -69.668648
18 true [4, 3] 60.282482 -22.938081 -10.333248 77.491642 35.583378 -69.668981
19 false [] 60.546226 0.668344 -1.025155 -0.869105 1.231405 0.548197

10. 结论

  1. ../Rvbust/uttc-20260428/ 目录里已有旧抓包相关轨迹资料,Config/Data/UTTC_MS11/ 目录里已有新版程序运行时导出,但在本次分析前,没有一份专门对应 2026042802-1.pcap 的“整条真实 J519 发包记录 + IO 标记”导出。
  2. 本次已经补齐该导出,文件位于 analysis/2026042802-1/2026042802-1_j519_actual_send_all_with_io.csv
  3. 2026042802-1.pcap 的真实 IO 触发次数是 17,与 ShotEvents.json 一致。
  4. 抓包中真实触发时使用的 IO 地址集合,与 UTTC_MS11.addr 一致;只有列表顺序差异,没有地址集合差异。
  5. 抓包中真实触发点相对示教点的平均最大单轴误差约 4.24 deg,最大达到 11.13 deg,主要偏在 J6J4
  6. 因此,如果后续要分析“为什么新程序拍照位置偏了”,不能把 2026042802-1.pcap 视为“与当前示教点完全重合的零误差基准”;它本身相对当前配置示教点就存在明显偏差。

11. 触发点前 6 个周期的实际关节坐标与示教点差值

在这份旧抓包里17 个真实触发点对应的“最接近示教点”的实际采样点,统一出现在触发前 6 个周期,也就是前 48ms

为了方便手工比对,这里把每个触发点前 6 个周期的实际关节坐标单独列出来,并和对应示教点逐轴做差。

说明:

  • pre6_frame / pre6_time 是触发帧前 68ms 周期的实际 J519 发包点。
  • pre6_j* 是该时刻的实际关节角度,单位 deg
  • diff_j* 定义为 pre6_actual - teach_point,单位 deg
  • 这张表更适合你直接和导出的触发序列逐点人工核对。
触发序号 waypoint_index pre6_frame pre6_time(s) pre6_J1 pre6_J2 pre6_J3 pre6_J4 pre6_J5 pre6_J6 diff_J1 diff_J2 diff_J3 diff_J4 diff_J5 diff_J6 max_abs_diff(deg) rms_diff(deg)
1 1 1941 5.840207 49.036247 2.169637 -10.893022 0.469577 6.183248 2.196172 +0.149437 -0.029348 +0.127996 +0.059368 -0.065133 -0.098818 0.149437 0.097560
2 2 2135 6.432175 55.402473 11.806953 -6.980721 -70.955170 6.007696 74.193436 +0.054718 -0.000086 +0.028375 +0.059162 -0.004369 -0.056096 0.059162 0.041764
3 3 2207 6.656264 55.185375 8.828770 -8.461165 -41.609406 9.991771 42.555542 +0.075567 +0.069272 +0.057051 -0.492155 -0.116717 +0.598609 0.598609 0.323506
4 4 2321 7.000291 43.639214 -1.593877 -17.673382 5.988685 32.060623 -22.587208 -0.014380 +0.035782 +0.042372 -0.006524 -0.111095 -0.013234 0.111095 0.051385
5 5 2457 7.416267 64.456329 4.153820 -15.749991 -29.951208 30.008862 45.422455 -0.126115 -0.109159 -0.080774 +0.001719 +0.158422 -0.204073 0.204073 0.129802
6 6 2649 7.976283 60.479656 23.070845 -5.011842 36.106964 5.525600 -31.304708 +0.000174 +0.002063 -0.000578 +0.000109 -0.000081 -0.002145 0.002145 0.001241
7 7 2795 8.424315 70.489815 16.270372 -13.455009 -28.079985 14.531018 31.910471 +0.013978 -0.123476 +0.001939 -0.187666 -0.004644 +0.198538 0.198538 0.122545
8 8 2892 8.712310 69.566750 -17.333443 -8.667440 -58.787853 7.566183 64.777092 -0.015715 -0.227729 +0.043149 -0.312159 -0.064407 +0.339359 0.339359 0.212417
9 9 3096 9.336375 73.577042 -6.447144 -9.620103 -128.858627 14.686206 140.051056 +0.005783 -0.017298 +0.008477 -0.049980 +0.015123 +0.049008 0.049980 0.030367
10 10 3154 9.512325 75.498016 -14.523483 -7.317341 -131.103271 17.629053 142.061157 -0.071370 +0.155823 -0.023185 -0.180238 -0.059308 +0.167649 0.180238 0.125181
11 11 3334 10.056378 86.017281 -14.446944 -13.689492 -69.926414 26.744770 89.069130 -0.076217 +0.051389 -0.007981 -0.058119 +0.002088 +0.069716 0.076217 0.052846
12 12 3455 10.424364 61.554295 -4.126511 -9.723098 -108.476807 22.091070 117.986275 -0.166438 +0.106278 +0.061325 -0.092724 -0.069702 -0.155789 0.166438 0.115819
13 13 3611 10.896324 80.232681 4.624288 -15.574081 -55.685375 26.385595 52.279755 +0.475598 -0.015943 -0.262896 +0.349937 +0.252457 +0.127497 0.475598 0.288100
14 14 3694 11.144347 108.790268 1.405766 -33.485344 -48.378551 43.857502 84.771103 -0.165283 +0.035006 +0.109081 -0.011190 -0.105902 -0.268158 0.268158 0.143580
15 15 3780 11.408361 110.543327 -3.739877 -32.432842 -50.983562 44.216587 87.566269 -0.041521 +0.121023 -0.036435 +0.129028 -0.012573 -0.057731 0.129028 0.079412
16 16 3854 11.632360 118.081940 -17.294281 -31.087101 -59.614193 48.711964 94.208046 -0.014013 +0.082106 -0.018100 -0.053655 -0.020612 +0.073275 0.082106 0.051540
17 18 4121 12.448375 60.258179 -22.880474 -10.315624 77.249168 35.501560 -69.439224 -0.024304 +0.057606 +0.017624 -0.242474 -0.081817 +0.229756 0.242474 0.142884

这张表说明:

  • 如果把旧程序的真实 IO 触发点整体前移 6 个周期,再去看对应关节坐标,它们和示教点已经非常接近。
  • 17 个点里,大多数前 6 周期采样点与示教点的最大单轴误差都在 0.05 ~ 0.34 deg
  • 误差较大的两个点是:
    • trigger 3max_abs_diff = 0.598609 deg
    • trigger 13max_abs_diff = 0.475598 deg
  • 这进一步支持一个很强的现象:旧抓包里的真实 IO 触发时刻,相对“最接近示教点”的实际运动位置,整体滞后约 48ms

12. 复现命令

在仓库根目录执行:

python analysis/analyze_2026042802_1_trigger_vs_teach_points.py
python analysis/analyze_2026042802_1_status_feedback_vs_teach_points.py

脚本默认读取:

  • ../Rvbust/uttc-20260428/2026042802-1.pcap
  • Config/RobotConfig.json
  • D:\Zyx\Downloads\WiresharkPortable32\App\Wireshark\tshark.exe

如果后续 Wireshark 路径变化,需要先同步修改脚本中的 DEFAULT_TSHARK

13. 60015 状态反馈视角复核

上面第 11 节的“前 6 个周期最接近示教点”,只是在看:

  • 192.168.10.10 -> 192.168.10.11 的 64B J519 下发命令序列

它说明的是“命令目标流”和示教点之间的相对时序,不能直接等价成“机械臂实时反馈已经过了示教点才触发”。

针对你补充的 buffer_size 线索,这里进一步直接解析了:

  • 192.168.10.11 -> 192.168.10.10 的 132B UDP 60015 状态包
  • 解码口径与仓库运行时代码 src/Flyshot.Runtime.Fanuc/Protocol/FanucJ519Protocol.cs 一致,全部按大端解析
  • 关节反馈字段使用状态包里的 0x3C ~ 0x57 六轴 float32 deg

本次新增文件:

  • analysis/2026042802-1/2026042802-1_j519_status_feedback_all.csv
  • analysis/2026042802-1/2026042802-1_trigger_status_feedback_vs_teach_points.csv
  • analysis/2026042802-1/2026042802-1_trigger_manual_compare.csv
  • analysis/2026042802-1/2026042802-1_status_feedback_summary.json
  • analysis/analyze_2026042802_1_status_feedback_vs_teach_points.py

13.1 序列号关系先确认

整条抓包里共提取到:

  • 状态包 1789
  • 命令包 1788

对每一条命令帧,取它前一个状态帧做对齐,结果是:

  • 命令 sequence - 前一条状态 sequence = 8
  • 统计结果:1788 / 1788 全部成立

这说明在这份旧抓包里,buffer_size=8 不是偶发现象,而是整条 J519 流都稳定成立。

换句话说:

  • 触发命令包上的 sequence=1381124
  • 它对应“当前实时反馈”的状态包 sequence=1381116
  • 中间固定隔着 8 个状态周期,也就是约 64ms

13.2 触发当下的实时反馈,并不在示教点附近

如果直接取“每个触发命令帧对应的当前状态包”,也就是:

  • paired_status_sequence = trigger_sequence - 8

那么 17 个点相对示教点的误差反而更大:

  • 平均最大单轴误差:6.469219 deg
  • 最大单轴误差:16.123934 deg
  • 最大误差轴分布:J6=9J2=4J1=2J4=2

这说明:

  • 机械臂在触发命令真正下发的那个时刻,对应的实时反馈位置,大多数情况下还没有跑到示教点附近
  • 所以“最接近示教点都出现在触发前 6 个周期”这个现象,不能理解成“机器人已经过了示教点才开始触发”
  • 那只是命令流相对示教点的时序关系;实时反馈要再往后看

13.3 实时反馈最接近示教点,出现在触发后 9 到 10 个状态周期

对每个触发点,以 paired_status_sequence = trigger_sequence - 8 为起点,在前后 ±20 个状态周期里搜索“与示教点最接近”的状态反馈帧,结果统一得到:

  • 最佳点相对 paired_status910 个状态周期
  • 统计分布:9 周期 = 9 次,10 周期 = 8

折算成触发命令时刻:

  • 最佳点相对触发命令晚 12 个状态周期
  • 统计分布:+1 周期 = 9 次,+2 周期 = 8
  • 对应抓包时间差约 71.838 ~ 79.964 ms
  • 平均约 75.661 ms

这里看起来像是 1~2 周期,但要注意基准不同:

  • 触发命令本身领先当前状态 8 个周期
  • 所以“触发后约 72~80ms 才最接近示教点”与“状态流从当前反馈再往后走 9~10 个周期才到位”本质上是同一件事

13.4 实时反馈视角的逐点结果

触发序号 waypoint_index trigger_frame trigger_seq paired_status_frame paired_status_seq 触发当下最大误差轴 触发当下最大误差(deg) 触发当下RMS(deg) 最佳状态seq 最佳状态相对当前反馈晚几周期 最佳状态相对触发时间(ms) 最佳状态最大误差(deg) 最佳状态RMS(deg)
1 1 1955 1381124 1954 1381116 J1 6.588242 3.822544 1381126 10 79.853 0.207590 0.122397
2 2 2151 1381198 2150 1381190 J1 2.335315 1.382754 1381199 9 71.912 0.082134 0.046161
3 3 2223 1381226 2222 1381218 J6 16.123934 8.852377 1381228 10 79.929 0.569692 0.323964
4 4 2336 1381269 2335 1381261 J6 4.901883 3.244056 1381271 10 79.909 0.094704 0.065805
5 5 2477 1381321 2476 1381313 J6 9.219438 4.621321 1381323 10 79.833 0.056622 0.033575
6 6 2665 1381391 2664 1381383 J6 6.146461 3.047841 1381392 9 71.904 0.176974 0.085631
7 7 2811 1381447 2810 1381439 J6 13.151718 7.782024 1381448 9 71.873 0.306587 0.182107
8 8 2908 1381483 2907 1381475 J6 7.436279 5.127041 1381484 9 71.838 0.060748 0.027507
9 9 3112 1381561 3111 1381553 J6 5.585605 3.277206 1381562 9 71.873 0.152260 0.089150
10 10 3170 1381583 3169 1381575 J2 4.165077 2.128984 1381585 10 79.856 0.073440 0.043286
11 11 3350 1381651 3349 1381643 J2 1.830953 1.169340 1381652 9 71.898 0.077668 0.042465
12 12 3470 1381697 3469 1381689 J4 4.822353 2.861125 1381698 9 71.950 0.021077 0.014936
13 13 3627 1381756 3626 1381748 J4 8.152508 5.168659 1381757 9 71.855 0.145543 0.069960
14 14 3710 1381787 3709 1381779 J6 10.404419 5.679811 1381789 10 79.922 0.119453 0.058360
15 15 3796 1381820 3795 1381812 J2 2.616108 1.591175 1381822 10 79.926 0.149803 0.087169
16 16 3870 1381848 3869 1381840 J2 4.682474 2.206740 1381850 10 79.964 0.172956 0.104194
17 18 4138 1381950 4137 1381942 J6 1.813960 1.153699 1381951 9 71.948 0.074923 0.038345

13.4.1 手工核对专用三时刻对照

如果你后面要拿着导出的触发序列人工对比,最推荐直接看下面这张表。

对应单独导出文件:

  • analysis/2026042802-1/2026042802-1_trigger_manual_compare.csv

它把每个触发点压缩成 3 个时刻:

  • 触发命令时刻
    • 你真正发出去、同时带着 IO 触发的那条命令
  • 触发当下实时反馈
    • buffer_size=8 回推到当时机器人真实反馈所对应的状态包
  • 最接近示教点的实时反馈
    • 在状态流中真正最贴近示教点的那一帧

可以直接把它理解成一句话:

  • “我在第几条命令上触发了”
  • “触发那一刻机器人实际还差多少”
  • “再过多久机器人才真正到最接近示教点的位置”
触发序号 waypoint_index 触发命令seq 触发命令frame 触发当下状态seq 触发当下状态frame 命令领先状态(周期) 触发当下最大误差(deg) 最接近示教点状态seq 最接近示教点状态frame 从触发当下反馈再晚几周期 相对触发命令再晚几周期 相对触发命令再晚多久(ms) 最接近示教点最大误差(deg)
1 1 1381124 1955 1381116 1954 8 6.588242 1381126 1977 10 2 79.853 0.207590
2 2 1381198 2151 1381190 2150 8 2.335315 1381199 2174 9 1 71.912 0.082134
3 3 1381226 2223 1381218 2222 8 16.123934 1381228 2248 10 2 79.929 0.569692
4 4 1381269 2336 1381261 2335 8 4.901883 1381271 2361 10 2 79.909 0.094704
5 5 1381321 2477 1381313 2476 8 9.219438 1381323 2502 10 2 79.833 0.056622
6 6 1381391 2665 1381383 2664 8 6.146461 1381392 2687 9 1 71.904 0.176974
7 7 1381447 2811 1381439 2810 8 13.151718 1381448 2833 9 1 71.873 0.306587
8 8 1381483 2908 1381475 2907 8 7.436279 1381484 2930 9 1 71.838 0.060748
9 9 1381561 3112 1381553 3111 8 5.585605 1381562 3134 9 1 71.873 0.152260
10 10 1381583 3170 1381575 3169 8 4.165077 1381585 3195 10 2 79.856 0.073440
11 11 1381651 3350 1381643 3349 8 1.830953 1381652 3372 9 1 71.898 0.077668
12 12 1381697 3470 1381689 3469 8 4.822353 1381698 3492 9 1 71.950 0.021077
13 13 1381756 3627 1381748 3626 8 8.152508 1381757 3649 9 1 71.855 0.145543
14 14 1381787 3710 1381779 3709 8 10.404419 1381789 3735 10 2 79.922 0.119453
15 15 1381820 3796 1381812 3795 8 2.616108 1381822 3821 10 2 79.926 0.149803
16 16 1381848 3870 1381840 3869 8 4.682474 1381850 3895 10 2 79.964 0.172956
17 18 1381950 4138 1381942 4137 8 1.813960 1381951 4160 9 1 71.948 0.074923

这张表最适合回答两个实际问题:

  1. 触发到底是不是已经“跑过点”才发生的

    • 不是
    • 因为触发当下对应的实时反馈误差仍然明显存在
  2. 真正最接近示教点的实时位置,距离触发还差多久

    • 大部分点还要再过 12 个命令周期
    • 如果换成状态反馈基准,就是再过 910 个状态周期
    • 时间量级稳定在 72~80ms

13.4.2 可以优先人工盯的异常点

如果你不想一开始就看 17 个点,建议优先盯下面几类:

  • trigger 3
    • 触发当下实时反馈最大误差 16.123934 deg
    • 即便到“最佳反馈时刻”,最大误差也还有 0.569692 deg
    • 它是 17 个点里最值得优先复核的一个
  • trigger 7
    • 触发当下实时反馈最大误差 13.151718 deg
    • 到位后仍有 0.306587 deg
  • trigger 14
    • 触发当下实时反馈最大误差 10.404419 deg
    • 属于第二档明显偏大的点
  • trigger 1
    • 触发当下误差 6.588242 deg,而且最大误差轴是 J1
    • 和前面很多 J6 主导的点不一样,适合拿来对比不同类型

如果你想最快抓住旧程序补偿逻辑,建议先人工对照:

  • trigger 3
  • trigger 7
  • trigger 14
  • trigger 1

这 4 个点已经能覆盖:

  • J6 主导的大偏差
  • J1 主导的特殊点
  • +1 周期到位与 +2 周期到位两种情况

13.5 对“为什么触发时间看起来在我运动到这个位置之后”的解释

现在可以把两种视角彻底分开:

  1. 命令流视角

    • 触发命令帧前 6 个周期的目标点,和示教点已经非常接近
    • 所以如果只看“发给机器人什么目标”,会误以为触发发生在“经过示教点之后”
  2. 实时反馈视角

    • 由于 buffer_size=8,触发命令帧本身就领先当前状态 8 个周期
    • 触发时刻对应的实时反馈,其实还没到示教点附近
    • 再往后走 9~10 个状态周期,反馈才最接近示教点

因此更准确的表述应该是:

  • 旧程序不是“机器人已经跑过示教点才开始触发”
  • 而是“触发命令是按一个提前缓存的目标序列发出去的,命令序列本身比实时反馈领先 8 个周期”
  • 叠加旧程序里触发标志在目标序列上的布置位置,就会出现:
    • 命令流看起来:最接近示教点在触发前 6 个周期
    • 实时反馈看起来:最接近示教点在触发后 9~10 个状态周期

两者并不矛盾,基准不同而已。

13.6 本节结论

  1. 这份旧抓包的 60015 状态流已经直接证明:下发命令序列稳定领先当前状态序列 8 个周期。
  2. 因此不能再把“命令流中的前 6 周期最接近示教点”误解成“机械臂真实位置已经过点后才触发”。
  3. 按实时反馈看17 个触发点在触发当下都还没有到示教点附近;最接近示教点的反馈统一出现在触发后约 72~80ms
  4. 如果后续要反推旧程序拍照补偿逻辑,优先应该建模成:
    • 命令目标序列 提前缓存 8 个周期
    • 触发标志 在目标序列上又相对示教点提前约 6 个周期
    • 两者叠加后,实时反馈到位时刻落在触发之后约 1~2 个命令周期

14. TriggerSampleIndexOffsetCycles 取值 6 / 7 / 8 的对照

在代码里把触发绑定后移做成显式配置之后,进一步对 6 / 7 / 8 三档做了离线对照。

本节的比较口径是:

  • 先找“最接近示教点”的最佳命令 sample
  • 再分别向后偏移 6 / 7 / 8 个命令周期
  • 比较这三档各自落到的命令帧,与旧抓包真实触发帧的误差大小

新增文件:

  • analysis/2026042802-1/2026042802-1_trigger_offset_6_7_8_compare.csv
  • analysis/2026042802-1/2026042802-1_trigger_offset_6_7_8_summary.json
  • analysis/build_trigger_offset_compare_6_7_8.py

14.1 汇总结论

三档比较结果非常明确:

  • 6 周期胜出 17 / 17
  • 7 周期胜出 0 / 17
  • 8 周期胜出 0 / 17

平均误差也呈现单调变差:

偏移周期 平均最大单轴误差(deg) 平均RMS误差(deg)
6 4.241584 2.540614
7 5.068092 3.021297
8 5.931684 3.520176

这说明:

  • 如果目标是“尽量复刻旧抓包里的真实命令触发点”,TriggerSampleIndexOffsetCycles 应该选 6
  • 当前写入配置的 7,更偏向“再往后推一拍”,但它并不更接近这份旧抓包

14.2 逐点对照表

触发序号 waypoint_index 最佳sample frame 偏移6 frame 偏移6最大误差 偏移6RMS 偏移7 frame 偏移7最大误差 偏移7RMS 偏移8 frame 偏移8最大误差 偏移8RMS 本点最优偏移
1 1 1941 1955 3.406527 2.468439 1958 4.180932 2.935578 1961 5.004552 3.418447 6
2 2 2135 2151 2.816723 1.656246 2153 3.526760 2.060948 2155 4.301479 2.503393 6
3 3 2207 2223 11.130744 5.955608 2227 13.165517 7.035338 2229 15.212756 8.119717 6
4 4 2321 2336 2.311684 1.373158 2339 3.083186 1.743718 2341 3.952016 2.158078 6
5 5 2457 2477 2.171828 1.404704 2479 2.585320 1.667010 2483 3.004922 1.950746 6
6 6 2649 2665 1.408994 0.842403 2667 1.770170 1.066919 2669 2.303903 1.330975 6
7 7 2795 2811 7.194107 4.618545 2814 8.248680 5.331516 2817 9.275559 6.035874 6
8 8 2892 2908 5.939884 3.533133 2910 6.922169 4.090128 2912 7.916950 4.649087 6
9 9 3096 3112 2.524071 1.630593 3114 2.830116 1.857618 3118 3.101311 2.072647 6
10 10 3154 3170 3.392997 2.105986 3172 4.172522 2.562682 3175 4.999007 3.042228 6
11 11 3334 3350 2.984824 2.013493 3352 3.668105 2.458222 3355 4.397696 2.932320 6
12 12 3455 3470 4.000958 1.990246 3474 4.929097 2.393668 3476 5.929425 2.827363 6
13 13 3611 3627 6.656209 4.109282 3629 7.722974 4.798702 3633 8.793828 5.501948 6
14 14 3694 3710 2.935180 1.567983 3712 3.224395 1.721797 3714 3.454963 1.844232 6
15 15 3780 3796 2.573147 1.686690 3798 3.074261 2.037358 3801 3.579433 2.398059 6
16 16 3854 3870 3.810522 2.197783 3872 4.756826 2.710410 3874 5.783033 3.263711 6
17 18 4121 4138 6.848522 4.036140 4140 8.296536 4.890438 4142 9.827793 5.794164 6

14.3 推荐

因此这里可以分成两个不同目标:

  1. 如果目标是“尽量复刻旧程序抓包”

    • 推荐:TriggerSampleIndexOffsetCycles = 6
  2. 如果目标是“主观上想往实时反馈更靠后推一点,再试效果”

    • 可以继续保留 7
    • 但要明确:这已经不是“最接近旧抓包”的取值,而是主动做新的补偿尝试

当前基于旧抓包证据,我更推荐:

  • 先把配置从 7 改回 6
  • 再拿新程序实际导出的 ActualSendJointTraj.txt + ShotEvents.json + 现场图像效果 做下一轮闭环