Files
cnc_wams/web-rtcp-5axis-sim-plan/working_run/01-run-gcode-execution-principle-and-test.md

571 lines
15 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.
# 01 RUN 与 G-code 执行原理测试记录
生成时间2026-06-22
## 1. 参考接续文件
本记录按 `textbak` 中的接续要求核对,不把 UI fixture、自增行号、JS-owned CNC 语义当作真实运行证明。
关键约束来自:
```text
textbak/text2.txt
- 数控/G-code/remap/tool/parameter/planner/kinematics/user-M 语义必须来自 LinuxCNC upstream 或 vendored LinuxCNC C/C++ source。
- UI 已优先解析 run_stepfallback 到 canonical event。
- 不引入 JS G-code parser 或 JS remap/tool/parameter semantics。
textbak/text40.txt
- LinuxCNC 可加载 kinematics module 的独立 WASM ABI 已完成。
- 下一步应转向 Web/M4/M5 simulation UI 接入 createLinuxCncKinematicsSdk() 输出,或推进 remap/planner/browser 集成验证。
textbak/text41.txt
- Web/M4/M5 当前状态曾明确记录为 RUN/STEP/JOG/HOME 仍推进 fixture axis pose。
- 目标是 RUN/STEP/JOG/HOME 消费 LinuxCNC kinematics frame contract。
- program execution 如果仍是 fixture line playback必须继续标记为 fixture/interpreter-not-connected。
- remap/planner/browser full-process 不得因 kinematics 接入而自动 promotion。
```
当前源码已经比 `text41.txt` 的早期基线多接入了 interpreter、kinematics、task/HAL runtime`RUN` 的连续执行模型仍需要按 task/HAL feedback 口径复核。
## 2. 当前 G-code 加载过程
启动入口:
```text
app/src/main.js:17-23
```
页面启动时创建 store、挂载 gmoccapy UI并异步附加
```text
1. LinuxCNC INI config
2. LinuxCNC kinematics runtime
3. LinuxCNC interpreter runtime
4. LinuxCNC task/HAL runtime
5. machine file staging
```
默认程序加载:
```text
app/src/main.js:103-124
```
`ensureDefaultLinuxCncProgramPreview()` 在 interpreter 与 machine files staged 后选择默认 LinuxCNC 5-axis source然后 dispatch
```text
LOAD_LINUXCNC_GCODE_SOURCE
```
选择 vendored G-code 后store 的处理链是:
```text
app/src/state/store.js:744-792
```
实际步骤:
```text
1. 从 machineFileStaging.save.files 中找到 sourceRel 对应的 G-code 文本。
2. selectMachineFileProgram(...) 生成该 G-code 对应的 machine-file plan。
3. buildLoadedProgram(...) 将文本拆成 programLines设置 activeProgram、programStartLine=1、activeLine=1。
4. 设置 programSource=linuxcnc-vendored-5axis-gcode。
5. 如果 task/HAL runtime 已加载initializeTaskHalSession({ openProgram: true })。
6. 如果 interpreter runtime 已加载dispatch RUN_INTERPRETER_PROGRAM。
```
本地 operator 打开文件时走同一个 `buildLoadedProgram(...)`
```text
app/src/state/store.js:1033-1057
```
G-code 文本显示在 UI
```text
app/src/ui/gmoccapy-shell.js:193-228
```
这里使用 `state.programLines` 逐行渲染,`state.activeLine` 对应的行加 `.active`,同时显示:
```text
Current line ${state.activeLine}
${state.activeLine} / ${programEndLine}
```
所以“能否看到 G-code 程序”和“是否逐行显示当前行”取决于 `programLines``activeLine` 是否被同一执行反馈链正确更新。
## 3. interpreter 解释与 preview 过程
`RUN_INTERPRETER_PROGRAM` 不是机床运行按钮,它是在加载程序后调用 LinuxCNC interpreter runtime生成 canonical motion / summary / timing 基础数据:
```text
app/src/state/store.js:468-508
```
解释完成后:
```text
app/src/state/store.js:509-555
```
当前实现会:
```text
1. 保存 programExecution。
2. buildTimingForState(...) 构建 programExecutionTiming。
3. 设置 programExecutionSourceMode=execution.sourceMode。
4. 设置 programExecutionMotionIndex=0、programExecutionSampleIndex=0。
5. 根据 firstMotion 更新 activeLine、axisPose、kinsType、rtcpState。
6. 创建 firstFeedback并更新 feed.currentVelocity。
```
需要注意:这里的 `axisPoseFromCanonicalMotion(firstMotion, ...)` 来自 canonical motion 事件,是预览/初始反馈,不等价于运行态 task/HAL 连续反馈。
## 4. timing 与速度来源
`buildTimingForState(...)` 的优先级:
```text
app/src/state/store.js:2369-2379
```
如果 `execution.plannerTiming.plannerRuntimeReady === true`,使用 plannerTiming否则调用
```text
buildProgramExecutionTiming(...)
```
该 timing fallback 的语义边界明确是估算:
```text
app/src/runtime/execution-timing.js:50-53
semanticBoundary: linuxcnc_canonical_motion_timing_estimate_not_planner_queue
sourceBasis: LinuxCNC canonical motion events plus INI/profile velocity limits and feed overrides
```
fallback timing 的速度计算逻辑:
```text
app/src/runtime/execution-timing.js:75-123
```
它按 canonical motion 段计算:
```text
1. rapid 使用 maxLinearVelocity * rapidOverride。
2. feed 使用 feedRate * feedOverride。
3. linearSeconds = linearDistance / linearVelocity。
4. angularSeconds = angularDistance / angularVelocity。
5. durationSeconds = max(linearSeconds, angularSeconds)。
```
这不是 LinuxCNC task/motion planner queue 的真实 run_step 推进。除非 `plannerTiming` 已 ready否则“实际时间”和“实际进给速度”只能说是 canonical-based estimate。
## 5. RUN 按钮当前执行原理
`RUN` action 入口:
```text
app/src/state/store.js:1059-1100
```
分两条路径。
### 5.1 task/HAL runtime loaded 路径
如果 `state.taskHalRuntime?.loaded` 为 true`RUN` 执行:
```text
app/src/state/store.js:1066-1072
```
发送命令:
```text
EMC_TASK_SET_STATE ON
EMC_TASK_SET_MODE AUTO
EMC_TASK_PLAN_RUN line = activeLine - 1
```
然后调用:
```text
runTaskHalCommandSequence(..., { taskCycles: 5 })
```
`runTaskHalCommandSequence` 的实现:
```text
app/src/state/store.js:1572-1604
```
当前真实顺序是:
```text
1. 标记 taskHalExecutionPending=true。
2. 如未初始化 task/HAL session则 initializeTaskHalSession/openProgram。
3. 逐个 sendCommand(command)。
4. 调用 taskHalRuntime.runCycles({ taskCycles: 5 })。
5. 调用 taskHalRuntime.readStatus()。
6. dispatch TASK_HAL_STATUS_APPLIED。
```
结论:
```text
当前 RUN 是“发送 task/HAL 命令 -> 主动推进固定 cycles -> 拉取一次 status -> 应用到 UI”。
源码中没有看到 task/HAL worker 在程序运行期间主动持续 postMessage status 的订阅/推送通道。
```
### 5.2 task/HAL runtime missing fallback
如果 task/HAL runtime 未加载,`RUN` 走:
```text
app/src/state/store.js:1074-1098
```
即:
```text
nextProgramRuntimeSamplePlayback(state, 5)
```
这个 fallback 按 sampleIndex 或 motionIndex 一次跳 5 步,更新 activeLine / axisPose / feed / runtimeFeedback。
语义边界在 feedback 中标记为:
```text
linuxcnc-tp-runtime-sample
linuxcnc_tp_run_cycle_feedback_without_hardware
```
或在没有 sample 时退回:
```text
linuxcnc-canonical-motion
linuxcnc_canonical_motion_feedback_without_tp_sample
```
这条路径不能当作真实 task/HAL 运行过程。
## 6. task/HAL feedback 如何映射到 UI 状态
task/HAL runtime 包装:
```text
app/src/runtime/linuxcnc-task-hal-runtime.js:96-114
```
暴露的是:
```text
sendCommand(command)
runCycles(options)
readStatus()
```
worker client
```text
app/src/runtime/linuxcnc-task-hal-worker-client.js:14-28
```
worker 本体:
```text
app/src/runtime/linuxcnc-task-hal-worker.js:5-17
app/src/runtime/linuxcnc-task-hal-worker.js:19-53
```
worker 当前是 request/response 模型:
```text
主线程 postMessage({ id, type, payload })
worker 执行 handleMessage(type, payload)
worker postMessage({ id, ok, result })
```
没有看到类似:
```text
subscribeStatus
onStatus
while running post feedback
```
因此,当前 task/HAL feedback 不是“运行时持续推送”,而是 UI/store 调用 `readStatus()` 得到的一次快照。
状态归一化:
```text
app/src/runtime/linuxcnc-task-hal-runtime.js:155-199
```
`normalizeTaskHalStatus()` 从 motion/HAL pins 生成:
```text
ui.taskState
ui.taskMode
ui.interpState
ui.taskCycle
ui.servoCycle
ui.motionQueueDepth
ui.activeLine
ui.switchkinsType
ui.axisPose
ui.currentVelocity
```
应用到 store
```text
app/src/state/store.js:1776-1835
```
`applyTaskHalStatusPatch(...)` 将 status 映射为:
```text
activeLine = programStartLine + ui.activeLine - 1
axisPose = resolveTaskHalAxisPose(...)
kinsType = resolveTaskHalKinsType(...)
rtcpState = rtcpStateFromKinsType(kinsType)
runState = 根据 interpState / paused / aborted / complete 推导
feed.currentVelocity = ui.currentVelocity
programRuntimeFeedback = createTaskHalRuntimeFeedback(...)
```
runtime feedback 字段:
```text
app/src/state/store.js:1925-1948
```
包括:
```text
sourceMode=linuxcnc-task-motion-hal-wasm
sampleIndex=ui.servoCycle
motionIndex=ui.activeLine - 1
line=activeLine
timeSeconds=ui.taskCycle * 0.01
axisPose
currentVelocityMmPerMin
requestedVelocityMmPerMin
distanceToGo
queueDepth
cycle
taskCycle
```
这里的问题是:如果 `readStatus()` 不持续发生,这些字段不会按真实执行时间继续变化。
## 7. 本地快照观察
说明:本轮浏览器采集脚本在用户中断前已写出完整 JSON 快照,作为辅助证据。正式结论仍以源码追踪为主。
辅助证据文件:
```text
web-rtcp-5axis-sim-plan/working_run/run-feedback-evidence/run-feedback-local.json
web-rtcp-5axis-sim-plan/working_run/run-feedback-evidence/run-feedback-local.png
```
快照摘要:
| 快照 | runState | activeLine | servoCycle | velocity | axisPose |
| --- | --- | ---: | ---: | ---: | --- |
| loaded-default | idle | 1 | 0 | 0 | X0 Y0 Z0 A0 B0 C0 |
| after-power | idle | 1 | 10 | 0 | X0 Y0 Z0 A0 B0 C0 |
| after-home | idle | 1 | 10 | 0 | X43 Y-32.15 Z-11.306 A0 B0 C0 |
| after-auto | idle | 1 | 20 | 0 | X0 Y0 Z0 A0 B0 C0 |
| after-run-idle | running | 5 | 70 | 3600 | X0 Y0 Z5 A0 B0 C0 |
| run-plus-0.5s-no-command | running | 5 | 70 | 3600 | X0 Y0 Z5 A0 B0 C0 |
| run-plus-2.0s-no-command | running | 5 | 70 | 3600 | X0 Y0 Z5 A0 B0 C0 |
| run-plus-5.0s-no-command | running | 5 | 70 | 3600 | X0 Y0 Z5 A0 B0 C0 |
观察结论:
```text
1. 点击 RUN 后,状态从 line 1 推进到 line 5servoCycle 从 20 推进到 70。
2. 后续 0.5s / 2.0s / 5.0s 没有再次调用命令时activeLine、servoCycle、axisPose、velocity 均保持不变。
3. 这与源码中的 runCycles({ taskCycles: 5 }) + readStatus() 一次相符。
4. 这不是连续 task/HAL feedback 推送模型。
```
## 8. 发现的问题
### P1. RUN 没有持续消费 task/HAL feedback
期望:
```text
G-code 程序执行时task/HAL/motion runtime 持续产生状态;
UI/store 持续消费这些状态;
当前行、轴值、速度、DTG、程序时间来自同一个 feedback source。
```
当前:
```text
RUN 只主动推进固定 taskCycles=5然后 readStatus 一次。
没有看到 worker 主动推送 status。
没有看到 store 中存在运行期间持续 readStatus/readEvents 的执行循环。
```
影响:
```text
RUN 后页面可能停在某个 status snapshot。
用户看到 runState=running但 activeLine、轴值、速度不会随真实时间继续推进。
```
### P1. 轴值存在状态回退/覆盖风险
快照中可见:
```text
after-home: X43 Y-32.15 Z-11.306
after-auto: X0 Y0 Z0
```
源码位置:
```text
app/src/state/store.js:1874-1901
```
`resolveTaskHalAxisPose()``ui.axisPoseFrame === "work"` 直接合并 task/HAL axisPose因此 task/HAL status 中的 0 位姿可覆盖 HOME 后的 fixture/work pose。
这解释了“现实的轴值都不对”的一类现象UI 显示值可能来自 task/HAL local/status 快照,而不是实际期望的机床/工件坐标连续反馈。
### P2. 程序时间和速度可能是 estimate 或单点 status
fallback timing 明确是:
```text
linuxcnc_canonical_motion_timing_estimate_not_planner_queue
```
task/HAL feedback 中的时间:
```text
timeSeconds = ui.taskCycle * 0.01
```
如果 taskCycle 不推进,程序时间也不推进。快照中 `taskCycle=0`,但 velocity 已为 3600说明当前 runtime feedback 的时间/速度字段并不构成可靠连续执行轨迹。
### P2. “逐行执行过程”目前只显示当前 activeLine不显示执行历史
UI 已显示:
```text
programLines
activeLine
gcode-row.active
progress
```
但没有看到 execution log / visited line history / per-feedback transcript。
因此用户能看到程序文本和当前行,但看不到完整“一行一行执行过程”的历史记录。
## 9. 正确的 RUN 执行模型建议
按你提出的标准,正确模型不应靠前端定时器自造轨迹,而应由 task/HAL/motion runtime 产出状态。
建议目标模型:
```text
1. RUN 只发送 LinuxCNC task command
EMC_TASK_SET_STATE ON
EMC_TASK_SET_MODE AUTO
EMC_TASK_PLAN_RUN
2. task/HAL runtime 在程序执行期间持续 run task/motion/servo cycles。
3. 每个 cycle 或每批 cycle 产生 LinuxCNC-owned feedback
activeLine
statement/source line
axisPose
jointPose
currentVelocity
requestedVelocity
distanceToGo / DTG
queueDepth
interpState
cycle / taskCycle / servoCycle
switchkinsType / RTCP state
4. worker 将 feedback 推送给 main thread或 main thread 通过明确的 runtime pump 读取。
5. store 只消费 feedback不自行按 motionIndex/sampleIndex 推进真实运行态。
6. UI 渲染:
- G-code 当前行高亮来自 feedback.line
- DRO 轴值来自 feedback.axisPose 或 kinematics frame
- 速度来自 feedback.currentVelocity
- 程序时间来自 feedback.timeSeconds
- 执行历史记录追加每次 feedback。
```
注意这里的“pump”可以在 worker 内部完成,也可以由 main thread 调用 runtime pump但语义必须是 LinuxCNC task/HAL/motion cycles 产生反馈,而不是 UI 自己按 wall clock 或固定 index 插值。
## 10. 后续测试建议
建议在 `working_run` 后续补一个严格测试脚本,断言以下行为:
```text
1. 加载一个短 G-code
G90 G21
G0 X0 Y0 Z5
G1 X10 F600
G1 Y10 F300
M30
2. RUN 后采集至少 5 个 feedback tick。
3. 断言每个 tick 的 sourceMode 都是 linuxcnc-task-motion-hal-wasm。
4. 断言 activeLine 按 feedback 推进,而不是固定跳到 line 5 后停止。
5. 断言 servoCycle/taskCycle 单调递增。
6. 断言 axisPose 与当前 G-code motion 目标/插补位置一致。
7. 断言 currentVelocity 与当前运动段 F 值/override/task status 对齐。
8. 断言 UI `.gcode-row.active`、DRO、runtime feedback 文本和 store state 使用同一个 feedback snapshot。
9. 断言执行历史中能看到逐条 line/tick 记录。
```
## 11. 本轮验证命令
已运行:
```bash
npm --prefix web-rtcp-5axis-sim-plan/app run build
```
结果:
```text
gmoccapy_static_build=ok
```
浏览器采集脚本在用户中断前已产生完整快照 JSON未作为正式 smoke 通过声明;本报告只把它作为辅助观察。
## 12. 总结
当前 `web-rtcp-5axis-sim-plan` 的 G-code 程序加载、文本显示、interpreter canonical motion、task/HAL 命令发送和 status 映射链路已经存在。
但当前 `RUN` 的核心行为仍是:
```text
发送命令 -> runCycles 固定 5 个 task cycles -> readStatus 一次 -> 更新 UI 一次
```
它没有形成“G-code 执行期间 task/HAL feedback 持续推送/持续消费”的运行态闭环。因此,按实际时间和进给速度连续执行、真实轴值连续更新、逐行执行过程可见,这三项目前都不能判定为满足。