feat(fanuc): 统一 speedRatio 执行倍率语义

* 将 speedRatio 前移到规划/准备阶段,运行时只消费已生成的 8ms 队列
* 区分旧格式规划导出与 ActualSend 实发诊断工件
* 补充普通轨迹、MoveJoint、飞拍队列和严格限幅回归测试
This commit is contained in:
2026-05-11 17:21:18 +08:00
parent d82128da4a
commit d120ef2a39
17 changed files with 1395 additions and 534 deletions

View File

@@ -47,10 +47,10 @@
本设计目标如下:
- 保持 `planning_speed_scale` 只属于规划层。
- `speedRatio` 只作用于飞拍执行层,且仅对下一次启动的飞拍任务生效
- 任意有效 `speedRatio` 下,最终真实发送的 `8ms` 点列必须满足逐周期 `vel/acc/jerk` 约束。
- `speedRatio` 是运行时统一执行倍率,可影响下一次启动的飞拍、普通轨迹和 `MoveJoint`
- 飞拍链路在任意有效 `speedRatio` 下,最终真实发送的 `8ms` 点列必须满足逐周期 `vel/acc/jerk` 约束。
- 不依赖“插值后自然会平滑”的经验假设。
-`speedRatio = 0.5` 等较慢倍率导致候选点列不满足约束时,系统自动拉长执行时长,直到点列通过校验
-请求 `speedRatio` 下的最终发送队列不满足约束时,拒绝执行并输出超限诊断;不在执行侧偷偷改写本次请求倍率
- 不破坏现有 `shot timeline / sample offset` 语义。
- `speedRatio = 1.0` 时行为应与当前基线一致或可解释等价。
@@ -58,7 +58,7 @@
本次设计明确不包含以下内容:
-修改 `move_joint` 的调速和生成机制
-恢复旧的发送阶段临场回采样机制;普通轨迹和 `MoveJoint` 允许把 `speedRatio` 前移到规划或临时轨迹生成阶段折算
- 不讨论 GUI、多机器人或旧 `50001/TCP+JSON` 网关恢复。
- 不修改 `planning_speed_scale` 的含义。
- 不在任务执行中途支持 `speedRatio` 热切换。
@@ -77,7 +77,7 @@
- 输入规划层稠密轨迹、执行时读取到的 `speedRatio`、机器人关节限位和触发时间轴。
- 生成最终 `8ms` 发送队列。
- 对最终队列做逐周期 `vel/acc/jerk` 校验。
- 如果不通过,则自动拉长执行时长后重建队列,直到通过
- 如果不通过,则拒绝执行,提示降低 `planning_speed_scale` 重新规划或显式设置更低的 `speedRatio`
3. 发送段
- 运行时只消费已经准备好的最终 `8ms` 队列。
- 运行时不再根据 `speedRatio``DenseJointTrajectory` 临场做线性回采样。
@@ -90,7 +90,7 @@
## 6. 统一语义
本次只改飞拍任务,但仍需把飞拍内部语义说清楚,避免再次混淆规划倍率和执行倍率。
本次飞拍任务为主线,但 `speedRatio` 允许作为运行时统一执行倍率影响普通轨迹和 `MoveJoint`,需要把三类入口的边界说清楚,避免再次混淆规划倍率和执行倍率。
### 6.1 `planning_speed_scale`
@@ -101,17 +101,17 @@
### 6.2 `speedRatio`
- 影响飞拍执行准备阶段
- 只对下一次启动的飞拍任务生效
- 表示“用户期望的执行层时间推进速度”
- 该值先用于构建候选发送队列
- 如果候选发送队列在离散 `8ms` 点列上不满足约束,则系统自动进一步拉长执行时长,直到满足约束
- 影响下一次启动的运动任务,不在任务执行中途热切换
- 对飞拍任务,表示用户期望的执行层时间推进速度,可前移到规划/执行准备阶段生成最终发送队列
- 对普通轨迹和 `MoveJoint`,允许在规划或临时 PTP 轨迹生成阶段折算进有效关节限位,使运行时只消费已经生成好的 `8ms` 稠密点列
- `speedRatio = 0.8` 表示执行节奏接近把同几何轨迹时间轴整体拉长 `1 / 0.8`
- 例如 `planning_speed_scale = 0.9``speedRatio = 0.8` 时,最终执行节奏应接近同几何轨迹在 `planning_speed_scale ≈ 0.72` 下的结果
也就是说:
- `speedRatio`执行目标倍率
- 但最终真实执行时长允许比该目标更保守
- 保守的原因不是放宽标准,而是严格保证离散动力学约束
- `speedRatio`确定的执行倍率,不是校验失败后的自动搜索变量
- `finalSpeedRatio` 正常必须等于请求值
- 若该倍率下最终队列超限,问题暴露给调用方,而不是由执行侧自动改成另一个倍率
## 7. 最终发送序列生成
@@ -124,54 +124,33 @@
职责:
- 输入规划层 `DenseJointTrajectory`
- 输入 `durationSeconds``servoPeriodSeconds``speedRatio`
- 输入规划层连续 `PlannedTrajectory`,优先使用经过执行侧定时处理后的 `ExecutionTrajectory`
- 输入 `servoPeriodSeconds``speedRatio`
- 输出最终真实发送的 `8ms` 点列。
- 输出与该点列一致的触发绑定结果。
- 输出校验与自动拉长过程中的诊断信息
- 输出请求倍率、最终倍率和历史倍率改写次数等诊断信息;当前语义下最终倍率等于请求倍率、改写次数为 0
### 7.2 第一版候选队列
### 7.2 最终请求倍率队列
候选队列仍可以沿用当前时间轴语义作为起点
最终队列按固定时间轴语义生成
- `sendTime = sampleIndex * 0.008`
- `trajectoryTime = min(sendTime * speedRatio, durationSeconds)`
- 关节目标从连续样条直接取点,并从 `rad` 转为 J519 需要的 `deg`
但该候选队列只作为“第一轮尝试”,不能直接视为最终执行结果
该队列就是本次请求倍率下的最终发送队列,后续只做校验和触发绑定,不再进行二次滤波或倍率搜索
### 7.3 自动拉长策略
### 7.3 校验失败策略
候选队列的离散 `vel/acc/jerk` 校验失败时:
最终队列的离散 `vel/acc/jerk` 校验失败时:
- 不修改规划层轨迹。
- 不修改 `planning_speed_scale`
- 不放宽校验阈值。
- 只在执行侧拉长最终发送时长,然后重新构建候选队列
- 不降低本次 `speedRatio`
- 直接拒绝执行并保留首个超限诊断。
可实现为等价的两种方式之一:
1. 延长物理发送总点数,使发送总时长变长。
2. 在保持 `8ms` 周期不变的前提下,降低执行侧有效轨迹时间推进速度。
本质上二者等价,建议统一落成第二种表达:
- 对外仍说“自动拉长执行时长”。
- 对内通过更保守的 `trajectoryTime(sendTime)` 映射来实现。
### 7.4 自动拉长的迭代规则
建议采用单调保守策略:
1. 先按请求 `speedRatio` 构建第 1 版候选队列。
2. 对候选队列做离散校验。
3. 若失败,则按固定步长或倍率逐轮拉长,再次构建候选队列。
4. 一旦通过,立即停止迭代,产出最终队列。
5. 若超过安全迭代上限仍未通过,则拒绝执行并输出首个超限诊断。
这样可以保证:
- 自动拉长过程是可解释、可记录的。
- 不会因为局部修补而引入新的不可预测尖峰。
这样可以避免把 `speedRatio` 误实现成安全优化器。现场若希望从 `planning_speed_scale = 0.9` 的 10s 轨迹降到接近 `planning_speed_scale = 0.7` 的执行效果,应显式设置类似 `speedRatio = 0.8`,而不是让系统在失败后自动猜一个更低倍率。
## 8. 逐周期约束校验
@@ -201,10 +180,11 @@
### 8.3 校验失败的处理
飞拍链路按本次确认采用 A
飞拍链路按当前修正语义采用“确定倍率 + 失败拒绝”
-失败,优先自动拉长执行时长重试
- 只有在超过拉长上限后仍失败时才拒绝执行
-请求倍率下失败,立即拒绝执行
- 不进行执行侧自动倍率搜索
- 调用方可以选择降低 `planning_speed_scale` 重新规划,或显式设置更低的 `speedRatio` 后再次执行。
拒绝执行时必须输出:
@@ -212,7 +192,8 @@
- 时间窗
- 指标类型
- `actual / limit / ratio`
- 失败发生在哪一轮自动拉长尝试中
- 请求 `speedRatio`
- 当前 `finalSpeedRatio`,正常等于请求值
## 9. 触发时序与绑定
@@ -238,15 +219,15 @@
- 触发绑定和真实发送完全一致。
- 导出工件中的 `ShotEvents.json` 与真实执行时序一致。
- 自动拉长后触发点可被精确追溯到最终发送点索引。
- 触发点可被精确追溯到本次请求倍率下的最终发送点索引。
### 9.3 关于触发数量与顺序
自动拉长执行时长时:
请求倍率改变最终发送时长时:
- 触发数量不能变。
- 触发顺序不能变。
- 只允许触发在最终发送队列中的绑定索引后移或保持可解释等价。
- 触发在最终发送队列中的绑定索引应随时间轴拉长后移或保持可解释等价。
## 10. 运行时职责调整
@@ -284,9 +265,19 @@
## 11. 导出工件与日志
### 11.1 工件统一基于最终发送队列
### 11.1 旧规划导出和实发诊断必须分开
以下文件必须全部从最终发送队列生成
`saveTrajectory` 目录中存在两类不同用途的文件,不能再混成同一条时间轴
第一类是旧格式规划导出,用于和旧 RVBUST 导出的规划轨迹、离线分析脚本做对比。它们只受 `planning_speed_scale` 和规划/整形参数影响,不受运行时 `speedRatio` 影响:
- `JointTraj.txt`
- `JointDetialTraj.txt`
- `CartTraj.txt`
- `CartDetialTraj.txt`
- `JointDetialTraj.analysis.txt`
第二类是实发诊断,用于观察当前 `speedRatio` 下真实准备发送的 8ms 队列和触发绑定。它们必须全部从最终发送队列生成:
- `ActualSendJointTraj.txt`
- `ActualSendTiming.txt`
@@ -295,7 +286,8 @@
不允许再出现:
- 导出文件基于一套样本
- 旧规划导出被运行时 `speedRatio` 改写
- `ActualSend*` 基于一套样本,
- 真实发送队列又基于另一套样本。
### 11.2 推荐新增日志字段
@@ -304,8 +296,8 @@
- 请求 `speedRatio`
- 规划层轨迹时长
- 第一轮候选队列点数
- 第一轮候选队列是否通过校验
- 最终发送队列点数
- 最终发送队列是否通过校验
- 若失败,首个失败窗口的:
- 关节轴
- 时间区间
@@ -313,7 +305,7 @@
- `actual`
- `limit`
- `ratio`
- 自动拉长轮数
- 倍率改写次数,当前正常为 0
- 最终采用的执行时长
- 最终发送队列点数
- 最终触发绑定数量
@@ -324,16 +316,16 @@
- `Information`
- 记录执行请求、最终采用结果、最终通过结论
- `Warning`
- 记录第一轮候选失败与自动拉长启动
- 记录请求倍率下最终发送队列校验失败
- `Debug`
- 记录每一轮拉长的中间参数与详细差分统计
- 记录最终发送队列的时间映射与详细差分统计
## 12. 验收口径
### 12.1 功能验收
- 飞拍 `speedRatio` 可在线设置。
- 该值对下一次启动的飞拍任务生效。
- `speedRatio` 可在线设置。
- 该值对下一次启动的飞拍、普通轨迹或 `MoveJoint` 生效。
- 不需要修改 `planning_speed_scale`
### 12.2 约束验收
@@ -345,8 +337,9 @@
### 12.3 工件与日志验收
- `JointTraj.txt / JointDetialTraj.txt / CartTraj.txt / CartDetialTraj.txt` 不随运行时 `speedRatio` 改变。
- `ActualSend*` 文件能反映最终真实发送点位与时间映射。
- 日志能定位自动拉长前后的关键参数和校验结果
- 日志能定位请求倍率、最终发送队列和校验失败窗口
### 12.4 回归验收
@@ -362,8 +355,10 @@
- 飞拍执行侧最终发送序列生成器测试
- `speedRatio` 非法边界值测试
- 第一轮候选失败后自动拉长成功测试
- 请求倍率下队列超限时拒绝执行测试
- `speedRatio = 1.0 / 0.8 / 0.5` 的逐周期限值通过测试
- `planning_speed_scale = 0.9``speedRatio = 0.8` 的执行时间轴等价于约 `0.72` 规划倍率的回归测试
- 普通轨迹和 `MoveJoint` 在倍率乘积相同时生成等价或可解释等价的稠密执行轨迹
- 触发绑定始终与最终发送队列一致的测试
### 13.2 编排测试
@@ -372,6 +367,8 @@
- `ExecuteFlyShotTraj` 进入运行时前,已经拿到最终发送队列
- `FanucControllerRuntime` 不再自行按 `_speedRatio` 对飞拍轨迹回采样
- `ExecuteTrajectory``MoveJoint` 的调速在规划/生成阶段完成,运行时不再做临场倍率重采样
- `SaveTrajectoryInfo` 的旧格式规划导出不随运行时 `speedRatio` 改变,而 `ActualSend*` 仍随运行时 `speedRatio` 改变
### 13.3 集成与黄金样本
@@ -401,13 +398,13 @@
新增飞拍执行侧最终发送序列生成器及相关结果类型,负责:
- 候选队列构建
- 自动拉长
- 离散校验
- 触发绑定输入准备
### 14.2 `src/Flyshot.ControllerClientCompat/`
在飞拍执行准备阶段调用该生成器,把最终发送队列放入执行结果或新的执行上下文对象。
普通轨迹规划入口和 `MoveJoint` 临时轨迹生成入口可以读取当前 `speedRatio`,并在进入运行时前折算成已调速的稠密轨迹。
### 14.3 `src/Flyshot.Runtime.Fanuc/FanucControllerRuntime.cs`
@@ -418,7 +415,10 @@
### 14.4 `src/Flyshot.ControllerClientCompat/FlyshotTrajectoryArtifactWriter.cs`
`ActualSend*` 导出改为复用最终发送队列,确保导出工件与运行时一致。
`FlyshotTrajectoryArtifactWriter` 接收两份视图:
- 旧格式规划导出视图:写 `JointTraj.txt``JointDetialTraj.txt``CartTraj.txt``CartDetialTraj.txt`
- 实发诊断视图:写 `ActualSendJointTraj.txt``ActualSendTiming.txt``ActualSendJerkStats.txt``ShotEvents.json`
### 14.5 `tests/Flyshot.Core.Tests/`
@@ -426,14 +426,15 @@
## 15. 风险与注意事项
### 15.1 不能把自动拉长误解为“偷偷改 speedRatio
### 15.1 不能把 speedRatio 做成安全优化器
对外语义仍应保留
对外语义必须保持
- 用户请求的是某个 `speedRatio`
- 系统为了满足离散动力学约束,自动采用了更保守的最终执行时长
- 系统按这个 `speedRatio` 构建最终发送队列
- 若队列超限,则拒绝执行并要求调用方显式选择新的规划倍率或执行倍率
日志里必须把这件事说清楚,不能让现场误以为参数未生效
不能因为校验失败就在执行侧自动把 `0.8` 改成 `0.2`;这会让 `speedRatio``planning_speed_scale` 的关系失去可解释性
### 15.2 不能让触发绑定脱离最终队列
@@ -448,10 +449,12 @@
本次飞拍调速设计的核心结论是:
- `planning_speed_scale` 继续只属于规划层。
- `speedRatio` 只属于飞拍执行层,并且只对下一次启动的飞拍任务生效。
- `speedRatio` 是运行时统一执行倍率,并且只对下一次启动的运动任务生效。
- 普通轨迹和 `MoveJoint` 允许把 `speedRatio` 前移到规划或临时轨迹生成阶段,运行时只发送已生成好的 `8ms` 点列。
- 飞拍执行必须先生成最终 `8ms` 发送队列,再对该队列做逐周期 `vel/acc/jerk` 校验。
- `speedRatio < 1` 导致第一轮候选队列不满足约束,则自动拉长执行时长后重建,直到通过
- `speedRatio``trajectoryTime = sendTime * speedRatio` 确定性拉长执行时间轴,`finalSpeedRatio` 不应被自动改写
- 若请求倍率下最终队列不满足约束,则拒绝执行并提示降低 `planning_speed_scale` 重新规划或显式设置更低 `speedRatio`
- 运行时不再对飞拍轨迹做发送前临场回采样。
- 触发绑定、导出工件与真实发送必须全部统一到同一份最终发送队列上。
- 旧格式规划导出必须保持不受运行时 `speedRatio` 影响;`ActualSend*``ShotEvents.json` 必须统一到真实发送队列上。
该方案可以在不修改 `move_joint`、不放宽校验阈值、不中断现有触发语义的前提下,把飞拍 `speedRatio` 调速从“经验插值”收敛为“可校验、可追踪、可自动保守化”的执行机制。
该方案可以在不放宽校验阈值、不中断现有触发语义的前提下,把 `speedRatio` 调速从“发送阶段经验插值”收敛为“规划/准备阶段完成、运行时确定发送”的执行机制。