Files
cnc_wams/web-rtcp-5axis-sim-plan/feed-rate-run-fix/README.md

727 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# G-code F 进给速度驱动 RUN 执行修复文档
生成时间2026-06-22
## 1. 问题结论
当前 `RUN` 链路已经能完成:
```text
1. 打开 LinuxCNC G-code 程序。
2. 通过 task/HAL runtime 推进状态。
3. 更新当前行、高亮行、DRO、axisPose、执行轨迹和 task/HAL feedback。
```
但当前 `RUN` **还没有按 G-code 的真实进给速度执行**
当前关键问题:
```text
G-code interpreter 能解析 F。
execution-timing.js 能基于 F / 距离 / INI 限速做 feed-based timing estimate。
但是 task/HAL RUN 执行链路没有把 F 用作真实运行速度。
C++ wrapper 层固定写入 velocity=60。
activeLine 按 task cycle 推进一行,而不是按 段距离 / F / elapsed time 推进。
```
因此现有浏览器 RUN 证据只能证明:
```text
task/HAL feedback 正在产生;
UI 当前行与 task/HAL active line 同步;
DRO 与 runtime axisPose 同步;
执行轨迹可见;
```
不能证明:
```text
程序按 G-code F 进给速度、G93/G94 模态、rapid/feed 区分、override、段距离真实定时执行。
```
## 2. 当前源码证据
### 2.1 固定 velocity=60
文件:
```text
wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_task_hal_wasm.cpp
```
函数:
```text
enqueue_linear_move_from_line(TaskRuntime &state, const std::string &line)
```
当前逻辑:
```cpp
command << ",\"velocity\":60}";
```
影响:
```text
1. 每条 G-code 运动行传给 motion runtime 的速度都是 60 units/s。
2. JS 状态层把 currentVel * 60 转成 mm/min。
3. 在 mm 单位下 UI 看到的速度固定为 3600 mm/min。
4. G-code 行中的 F159 / F318 / F636 等不会影响 RUN 实际速度。
```
### 2.2 按 task cycle 推进一行
文件:
```text
wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_task_hal_wasm.cpp
```
函数:
```text
lctask_run_cycles(long task_period_ns, long servo_period_ns, int task_cycles)
```
当前逻辑:
```cpp
for (int i = 0; i < task_cycles; ++i) {
state.task_cycle += 1;
if (state.interp_state == "READING" && state.next_program_line < state.opened_line_count) {
enqueue_linear_move_from_line(state, state.program_lines[state.next_program_line]);
...
}
lcmot_step_servo(...);
}
```
影响:
```text
1. 每个 task cycle 至多 enqueue 一条程序行。
2. active line 由 cycle 数推进。
3. 长段、短段、F 快、F 慢不会改变行推进节奏。
4. 这不是真实 LinuxCNC planner/task/motion 的时间语义。
```
### 2.3 interpreter 已有 F 信息
文件:
```text
web-rtcp-5axis-sim-plan/app/src/runtime/linuxcnc-interpreter-runtime.js
```
已有能力:
```text
1. feedRatesBySourceLine(programText) 从 G-code 中提取 F。
2. parseLinuxCncCanonicalMotion(...) 把 activeFeedRate 写入 motion event 的 feedRate。
3. linearUnitsBySourceLine(...) 跟踪 G20/G21。
```
输出 motion event 已包含:
```js
{
type,
line,
axes,
feedRate,
linearUnits,
...
}
```
### 2.4 timing estimate 已有 F 计算
文件:
```text
web-rtcp-5axis-sim-plan/app/src/runtime/execution-timing.js
```
已有能力:
```text
1. 按 motion event feedRate 计算 requestedLinearVelocity。
2. 按 profile/INI max velocity 限速。
3. 按 linear distance / angular distance 计算 segment durationSeconds。
4. 处理 feedOverride / rapidOverride。
```
但当前边界是:
```text
semanticBoundary = linuxcnc_canonical_motion_timing_estimate_not_planner_queue
```
说明它是估算,不是 task/HAL RUN 的权威执行节奏。
## 3. 修复目标
### 3.1 必须达成
修复后 `RUN` 必须满足:
```text
1. G-code 中 F 值进入 task/HAL RUN 执行链路。
2. G94 units/min 模式下feed move 的速度来自当前 modal F。
3. G0/STRAIGHT_TRAVERSE 使用 rapid velocity / rapid override / INI max velocity。
4. activeLine 按 elapsed execution time 与当前段完成度推进。
5. 长距离低 F 段明显运行更久。
6. 短距离高 F 段明显更快完成。
7. UI currentVelocity 不再固定 3600 mm/min。
8. DRO/axisPose 可以在段内插值,而不是只在行边界跳变。
9. 现有 RUN gate、task/HAL session、status loop、STOP/ABORT/STEP 不回退。
```
### 3.2 第一阶段不强求
以下可以作为第二阶段:
```text
1. 完整 LinuxCNC trajectory planner queue 动力学一致性。
2. 加速度/jerk/圆弧真实插补完全对齐 LinuxCNC。
3. G93 inverse-time feed 的完整五轴角度/线性混合真实语义。
4. 硬件 realtime HAL 驱动。
```
但第一阶段至少不能继续固定 `velocity=60`
## 4. 推荐实现方案
### 4.1 不要继续在 C++ wrapper 中逐行粗解析 G-code
当前 C++ wrapper 的 `enqueue_linear_move_from_line()` 是字符串扫描:
```text
查找 XYZABC 字母;
strtod 读取数值;
没有模态;
没有 F
没有 G90/G91
没有 G20/G21
没有 G93/G94
没有 G0/G1/G2/G3 区分;
```
这个方向继续扩展会很脆弱。
推荐改为:
```text
JS interpreter runtime 继续负责生成 canonical motion events。
JS 侧把 canonical motion + timing segments 传入 task/HAL runtime session。
C++ task/HAL wrapper 不再直接解析 G-code 行来生成 velocity。
C++ task/HAL wrapper 按已解析 motion segment 执行。
```
### 4.2 新增 task/HAL program motion plan
`wasm-port/runtime/sdk/src/linuxcnc-task-hal.js` 增加可选 API
```js
loadProgramMotionPlan({
programPath,
motion,
timing,
linearUnits,
})
```
在 C++ wrapper 增加导出:
```cpp
int lctask_load_program_motion_plan_json(const char *plan_json);
```
motion plan 中至少包含:
```json
{
"programPath": "...",
"segments": [
{
"line": 8,
"type": "STRAIGHT_FEED",
"motionClass": "feed",
"axes": { "x": 6.302, "y": -11.560, "z": 27.743, "a": -71.841, "c": -35.930 },
"startAxes": { "...": 0 },
"feedRate": 318,
"linearUnits": "mm",
"velocityMmPerMin": 318,
"durationSeconds": 0.42,
"startSeconds": 1.25,
"elapsedSeconds": 1.67
}
]
}
```
### 4.3 C++ task runtime 状态新增字段
`TaskRuntime` 增加:
```cpp
struct MotionSegment {
int line = 0;
std::string type;
std::string motion_class;
double start_seconds = 0.0;
double duration_seconds = 0.0;
double velocity_mm_per_min = 0.0;
std::map<std::string, double> start_axes;
std::map<std::string, double> end_axes;
};
std::vector<MotionSegment> motion_plan;
int active_segment_index = 0;
double run_elapsed_seconds = 0.0;
double run_start_seconds = 0.0;
```
### 4.4 RUN 周期推进改为按 elapsed time
当前:
```text
每 task cycle enqueue 一行。
```
修复后:
```text
每次 lctask_run_cycles 根据 task_period_ns * task_cycles 增加 run_elapsed_seconds。
根据 run_elapsed_seconds 找到 active segment。
按 segment progress 插值 axisPose。
把 currentVel/requestedVel 设置为 segment.velocityMmPerMin / 60。
activeLine = segment.line。
segment 完成后才进入下一 segment。
```
伪代码:
```cpp
int lctask_run_cycles(long task_period_ns, long servo_period_ns, int task_cycles)
{
const double delta_seconds = (task_period_ns / 1e9) * task_cycles;
state.run_elapsed_seconds += delta_seconds;
const MotionSegment *segment = find_segment_at_time(state.motion_plan, state.run_elapsed_seconds);
if (!segment) {
state.interp_state = "IDLE";
state.exec_state = "DONE";
return step_servo(...);
}
const double local = state.run_elapsed_seconds - segment->start_seconds;
const double progress = clamp(local / segment->duration_seconds, 0.0, 1.0);
AxisPose pose = interpolate(segment->start_axes, segment->end_axes, progress);
forward_motion_sample({
line: segment->line,
axes: pose,
currentVel: segment->velocity_mm_per_min / 60.0,
requestedVel: segment->velocity_mm_per_min / 60.0,
inPosition: progress >= 1.0
});
step_servo(...);
}
```
### 4.5 motion runtime command schema
当前 motion command 是:
```json
{
"type": "EMC_TRAJ_LINEAR_MOVE",
"line": 12,
"x": 1,
"velocity": 60
}
```
建议新增或扩展为:
```json
{
"type": "EMC_TRAJ_LINEAR_SAMPLE",
"line": 12,
"x": 1.2,
"y": 3.4,
"z": 5.6,
"a": -70,
"c": 20,
"currentVel": 5.3,
"requestedVel": 5.3,
"segmentProgress": 0.35,
"source": "feed_timed_motion_plan"
}
```
如果不想新增 command type也可以继续使用 `EMC_TRAJ_LINEAR_MOVE`,但必须:
```text
1. velocity 来自 segment.velocityMmPerMin / 60。
2. axes 是当前插值位置,而不是只用 segment end。
3. status 能保留 line/progress/currentVel。
```
## 5. G94 速度规则
### 5.1 G94 units per minute
`TRAJ.LINEAR_UNITS=mm` 时:
```text
F318 => 318 mm/min
```
`TRAJ.LINEAR_UNITS=inch` 或 G20 active 时:
```text
F10 => 10 inch/min => 254 mm/min
```
计算:
```text
velocity_mm_per_min = feedRate * linearUnitScaleToMm * feedOverride
duration_seconds = linearDistanceMm / (velocity_mm_per_min / 60)
```
### 5.2 Rapid
G0 / `STRAIGHT_TRAVERSE`
```text
velocity_mm_per_min = min(INI max velocity, profile max velocity) * rapidOverride
duration_seconds = distance / velocity
```
### 5.3 Angular axes
第一阶段可沿用 `execution-timing.js` 现有策略:
```text
linearSeconds = linearDistanceMm / linearVelocity
angularSeconds = angularDistanceDeg / angularVelocity
durationSeconds = max(linearSeconds, angularSeconds)
```
这至少能避免旋转轴运动被零时长吞掉。
### 5.4 G93 inverse time
LinuxCNC impeller 程序包含:
```text
G93
...
G1 ... F159
```
G93 的 F 不是 units/min而是 inverse time。第一阶段有两种策略
```text
方案 A先检测 G93明确标注 unsupportedRUN gate 阻止真实 feed mode 运行。
方案 B实现基础 inverse-timeduration_minutes = 1 / Fduration_seconds = 60 / F。
```
推荐:
```text
第一阶段实现方案 B。
```
原因:
```text
1. test_linuxcnc_source/impeller-7bl-xyzac.ngc 使用 G93。
2. 如果不支持 G93就无法验证用户当前指定测试文件的真实进给语义。
3. 对 inverse-timeF 直接定义该运动块完成时间,适合第一阶段验证。
```
需要在 interpreter motion event 中增加:
```js
feedMode: "inverse-time" | "units-per-minute"
```
或至少在 timing 阶段通过 source line 扫描维护 G93/G94 模态。
## 6. 代码修改落点
### 6.1 `linuxcnc-interpreter-runtime.js`
新增:
```text
feedModeBySourceLine(programText)
```
识别:
```text
G93 => inverse-time
G94 => units-per-minute
```
motion event 增加:
```js
feedMode: activeFeedMode
```
### 6.2 `execution-timing.js`
修改 `buildTimingSegment(...)`
```text
if event.feedMode === "inverse-time":
durationSeconds = 60 / feedRate
velocityMmPerMin = linearDistanceMm / durationSeconds * 60
else:
保持 G94 units/min 逻辑
```
注意:
```text
1. G93 中 F 必须大于 0。
2. G93 中如果缺 F应保留上一个 modal F 或报错,按 LinuxCNC 语义确认。
3. velocity 仍要可被 INI max velocity 限制还是忠实 inverse-time需要明确。第一阶段建议记录 requested 与 capped 两个值。
```
### 6.3 `store.js`
`initializeTaskHalSession` 后或 `RUN_INTERPRETER_PROGRAM` 完成后,把当前 `programExecution.motion``programExecutionTiming.segments` 传给 task/HAL runtime
```js
await state.taskHalRuntime.loadProgramMotionPlan({
programPath: session.programPath,
motion: state.programExecution.motion,
timing: state.programExecutionTiming,
linearUnits: state.profile.traj.linearUnits,
});
```
需要保证:
```text
1. LOAD_LINUXCNC_GCODE_SOURCE 后 interpreter 已完成。
2. task/HAL openProgram 的 programPath 与 motion plan programPath 一致。
3. 如果 motion plan 不存在RUN gate 应阻止“真实进给速度 RUN”不能悄悄回退 velocity=60。
```
### 6.4 `linuxcnc-task-hal.js`
新增 SDK 方法:
```js
loadProgramMotionPlan(plan) {
const rc = callWithJson(mod, "lctask_load_program_motion_plan_json", plan);
if (rc !== 0) throw new Error(...);
}
```
### 6.5 `linuxcnc_task_hal_wasm.hh`
新增声明:
```cpp
int lctask_load_program_motion_plan_json(const char *plan_json);
```
### 6.6 `linuxcnc_task_hal_wasm.cpp`
新增:
```text
1. MotionSegment 数据结构。
2. 简单 JSON plan parser或复用现有轻量 json_number_after 风格解析数组。
3. lctask_load_program_motion_plan_json。
4. lctask_run_cycles 按 elapsed time 找 segment。
5. 删除或隔离 fixed velocity=60 的 fallback。
```
要求:
```text
fixed velocity=60 只能作为 explicit fixture fallback。
真实 RUN 路径不允许使用它。
```
## 7. 测试计划
### 7.1 Node 单元测试F 值改变 duration
新增测试:
```text
web-rtcp-5axis-sim-plan/tests/node/verify_feed_rate_execution_timing.mjs
```
用例:
```gcode
G90 G94
G1 X10 F60
G1 X20 F600
M2
```
期望:
```text
第一段 10mm @ 60mm/min => 10s。
第二段 10mm @ 600mm/min => 1s。
duration ratio ≈ 10:1。
```
### 7.2 Node 单元测试G93 inverse time
用例:
```gcode
G90 G93
G1 X10 F2
G1 X20 F10
M2
```
期望:
```text
F2 => 60/2 = 30s。
F10 => 60/10 = 6s。
```
### 7.3 task/HAL runtime 测试:速度不固定
新增或扩展:
```text
web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs
```
断言:
```text
1. RUN feedback currentVelocityMmPerMin 不等于固定 3600。
2. 不同 F 段采样到不同 requested/current velocity。
3. activeLine 不再每 task cycle 固定递增一行。
4. 慢速段停留采样数 > 快速段。
```
### 7.4 浏览器证据测试
扩展:
```text
qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs
```
新增报告字段:
```text
feedMode
feedRate
segmentDurationSeconds
segmentProgress
requestedVelocityMmPerMin
currentVelocityMmPerMin
```
报告验收:
```text
1. impeller G93 行显示 inverse-time。
2. 采样 velocity 随当前 segment 变化。
3. 当前行停留时间与 F 值/段时长一致。
4. Word 报告中列出 F、feedMode、duration、velocity 的采样表。
```
## 8. 验收标准
修复完成必须通过:
```bash
node web-rtcp-5axis-sim-plan/tests/node/verify_feed_rate_execution_timing.mjs
node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs
node web-rtcp-5axis-sim-plan/tests/node/verify_linear_unit_conversion.mjs
npm --prefix web-rtcp-5axis-sim-plan/app run build
npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
node qa/web-rtcp-5axis-site-test/capture-test-linuxcnc-source-run.mjs
```
并满足:
```text
1. 不再出现所有 RUN 样本 velocity=3600 的固定速度现象。
2. C++ task/HAL RUN 主路径不再写死 velocity=60。
3. G94 F60/F600 用例体现 10:1 段时长差异。
4. G93 F2/F10 用例体现 5:1 段时长差异。
5. test_linuxcnc_source/impeller-7bl-xyzac.ngc 的 RUN 报告包含 feedMode/feedRate/duration 证据。
6. STOP/ABORT/STEP 现有测试不回退。
```
## 9. 风险与注意事项
### 9.1 不要伪造 LinuxCNC 语义
如果还没有完整 planner就必须在状态中明确
```text
semanticBoundary = feed_timed_canonical_motion_runtime
```
不要标称为真实 LinuxCNC planner。
### 9.2 不要把 estimate 当作硬件实时
第一阶段可以做到:
```text
canonical motion + feed mode + timing driven browser simulation
```
不能声称:
```text
hardware realtime LinuxCNC execution
```
### 9.3 G93 是当前 impeller 文件的关键
`test_linuxcnc_source/impeller-7bl-xyzac.ngc` 开头有:
```gcode
M428 ;TCP:xyzac
G93
S600 M3
```
因此如果不处理 G93针对该文件的“真实 F 速度”验证仍然不完整。
## 10. 建议实施顺序
```text
1. 给 interpreter motion event 增加 feedMode。
2. 给 execution-timing.js 增加 G93 duration。
3. 写 verify_feed_rate_execution_timing.mjs先证明 timing 正确。
4. 给 task/HAL SDK/WASM 增加 loadProgramMotionPlan。
5. 改 lctask_run_cycles按 elapsed time 和 segment progress 推进。
6. 改 store把 motion plan 注入 task/HAL session。
7. 扩展 verify_run_feedback_loop证明 velocity 不固定、慢段停留更久。
8. 扩展浏览器 RUN 报告,输出 feedMode/feedRate/duration/progress。
9. 重跑 build、smoke、浏览器报告。
10. 更新 working_run 和 gptlog-process。
```