# 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_step,fallback 到 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 5,servoCycle 从 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 持续推送/持续消费”的运行态闭环。因此,按实际时间和进给速度连续执行、真实轴值连续更新、逐行执行过程可见,这三项目前都不能判定为满足。