# 02 RUN 前置条件、详细测试与编写步骤 生成时间:2026-06-22 ## 1. 核心原则 `RUN` 不是孤立按钮逻辑。对五轴机床,必须先确定 LinuxCNC 机床上下文,再执行程序。 正确顺序是: ```text 明确 INI 文件 -> 确定机床类型、坐标轴、joint、HAL、remap、tool-table -> 确定五轴运动学算法和 switchkins 映射 -> 验证 kinematics WASM 和 task/HAL runtime 已处在同一机床上下文 -> 加载并打开 G-code -> RUN 发送 LinuxCNC task command -> 持续 runCycles/readStatus 或 worker push feedback -> UI 只消费 task/HAL/motion feedback ``` 不能先写一个通用 `RUN`,再猜测当前机床是什么。否则 `activeLine`、DRO、RTCP、A/B/C 旋转轴、TCP 补偿和 switchkins 状态都可能来自不同来源。 ## 2. 必须先确认的机床上下文 当前项目内可作为 `RUN` 上下文的五轴 profile 只有两个: | profile | LinuxCNC INI | 机床类型 | 坐标 | kinematics module | | --- | --- | --- | --- | --- | | `xyzac-trt` | `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini` | table rotary tilting | `XYZAC` | `xyzac-trt` | | `xyzbc-trt` | `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini` | table rotary tilting | `XYZBC` | `xyzbc-trt` | 源码落点: ```text app/src/profiles/index.js app/src/profiles/xyzac-trt.js app/src/profiles/xyzbc-trt.js app/src/runtime/linuxcnc-ini-runtime.js app/src/runtime/linuxcnc-kinematics-runtime.js wasm-port/runtime/sdk/src/linuxcnc-kinematics.js ``` ### 2.1 `xyzac-trt` 必须确认: ```text profile id: xyzac-trt INI: configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini [EMC] MACHINE: sim-xyzac-trt-kins (switchkins) [TRAJ] COORDINATES: XYZAC [KINS] KINEMATICS: xyzac-trt-kins sparm=identityfirst WASM module id: xyzac-trt WASM file: linuxcnc_xyzac_trt_kinematics.wasm LinuxCNC source: src/emc/kinematics/xyzac-trt-kins.c shared source: src/emc/kinematics/trtfuncs.c switchkins source: src/emc/kinematics/switchkins.c ``` switchkins 映射: ```text M429 -> switchkins type 0 -> identity M428 -> switchkins type 1 -> tcp-xyzac M430 -> switchkins type 2 -> userk HAL link: motion.analog-out-03 => motion.switchkins-type ``` ### 2.2 `xyzbc-trt` 必须确认: ```text profile id: xyzbc-trt INI: configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini [EMC] MACHINE: sim-xyzbc-trt-kins (switchkins) [TRAJ] COORDINATES: XYZBC [KINS] KINEMATICS: xyzbc-trt-kins sparm=identityfirst WASM module id: xyzbc-trt WASM file: linuxcnc_xyzbc_trt_kinematics.wasm LinuxCNC source: src/emc/kinematics/xyzbc-trt-kins.c shared source: src/emc/kinematics/trtfuncs.c switchkins source: src/emc/kinematics/switchkins.c ``` switchkins 映射: ```text M429 -> switchkins type 0 -> identity M428 -> switchkins type 1 -> tcp-xyzbc M430 -> switchkins type 2 -> userk HAL link: motion.analog-out-03 => motion.switchkins-type ``` ## 3. RUN 前置状态检查 `RUN` action 进入真正执行前,应要求这些状态全部明确: ```text state.profile.id 是 xyzac-trt 或 xyzbc-trt state.profile.iniPath 非空且已加载 state.iniConfigReadiness.loaded === true state.iniConfigReadiness.ready === true state.iniConfigReadiness.coordinates 与 profile.traj.coordinates 一致 state.profile.kinematicsModuleId 与 INI [KINS] KINEMATICS 推导结果一致 state.kinematicsRuntimeReadiness.loaded === true state.kinematicsRuntimeReadiness.moduleId === state.profile.kinematicsModuleId state.rtcpFrame.sourceMode === source-derived-kinematics-wasm state.machineFileStaging.status === staged state.taskHalRuntimeReadiness.taskRuntimeReady === true state.taskHalRuntimeReadiness.motionRuntimeReady === true state.taskHalRuntimeReadiness.halRuntimeReady === true state.taskHalSession.programPath 指向当前选中的 G-code ``` 如果任一项不满足,`RUN` 应返回明确 operatorMessage,不应进入 fixture line playback 后伪装成真实运行。 建议阻断消息: ```text run blocked: LinuxCNC INI not loaded run blocked: machine profile and INI coordinates mismatch run blocked: LinuxCNC kinematics runtime not ready run blocked: task/HAL runtime not ready run blocked: no machine-file G-code opened for task/HAL session ``` ## 4. 详细测试计划 ### 4.1 INI/profile 解析测试 目标:证明 `RUN` 使用的不是手写机床参数,而是明确 LinuxCNC INI。 测试文件建议: ```text tests/node/verify_run_preconditions.mjs ``` 断言: ```text 1. 读取 xyzac-trt.ini,parseLinuxCncIni() 返回: profileId=xyzac-trt machineName=sim-xyzac-trt-kins (switchkins) traj.coordinates=XYZAC kinematics.name=xyzac-trt-kins kinematicsModuleId=xyzac-trt validation.ready=true 2. 读取 xyzbc-trt.ini,parseLinuxCncIni() 返回: profileId=xyzbc-trt machineName=sim-xyzbc-trt-kins (switchkins) traj.coordinates=XYZBC kinematics.name=xyzbc-trt-kins kinematicsModuleId=xyzbc-trt validation.ready=true 3. applyIniConfigToProfile() 后: profile.traj.coordinates 与 INI 一致 profile.kinematicsModuleId 与 INI 推导结果一致 axisLimits 和 jointConfig 来自 INI ``` 命令: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs ``` 通过标准: ```text run_preconditions_ini_profile_smoke=ok ``` ### 4.2 运动学模块确认测试 目标:证明当前机床的五轴运动学算法已经按 INI/profile 加载。 断言: ```text 1. createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" }) 成功。 2. readiness.moduleId === "xyzac-trt"。 3. wasmFile === "linuxcnc_xyzac_trt_kinematics.wasm"。 4. frameForJoints([10,20,30,25,40]) 返回 moduleId=xyzac-trt。 5. switchKinematics(1) 后 switchkinsType=1,frame source 仍是 linuxcnc_kinematics_wasm_c_abi。 6. 对 xyzbc-trt 重复同样测试。 ``` 已有相关测试: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_kinematics_runtime.mjs ``` 新增 RUN 前置测试应把这个结果纳入 `RUN` gate,而不是只作为独立 smoke。 ### 4.3 machine-file staging 测试 目标:证明 `RUN` 打开的程序、INI、remap、tool table 是同一个 LinuxCNC 机床目录上下文。 断言: ```text 1. stageProfileMachineFiles(profile) 成功。 2. save.files 中包含: - 当前 profile 的 INI - 当前 profile 的 tool table - remap_subs/428remap.ngc - remap_subs/429remap.ngc - remap_subs/430remap.ngc - 当前 profile 的 demo G-code 3. buildTaskHalSessionFromMachineFiles() 生成: profileId 与 state.profile.id 一致 iniPath 与 profile.iniPath 一致 programPath 指向当前 selectedGcodeSourceRel ``` 已有相关测试: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_machine_file_staging.mjs ``` ### 4.4 RUN 连续 feedback 测试 目标:证明点击 `RUN` 后,不是只推进一次 `taskCycles=5`,而是持续从 task/HAL/motion 获取反馈。 测试程序: ```ngc G90 G21 M429 G0 X0 Y0 Z5 A0 C0 M428 G1 X10 Y0 Z5 A10 C20 F600 G1 X10 Y10 Z0 A20 C30 F300 M429 G1 X0 Y0 Z5 A0 C0 F600 M30 ``` 对 `xyzbc-trt` 时把 A 轴替换为 B 轴: ```ngc G90 G21 M429 G0 X0 Y0 Z5 B0 C0 M428 G1 X10 Y0 Z5 B10 C20 F600 G1 X10 Y10 Z0 B20 C30 F300 M429 G1 X0 Y0 Z5 B0 C0 F600 M30 ``` 采集至少 8 个 feedback tick,断言: ```text 1. 每个 tick 的 sourceMode 都是 linuxcnc-task-motion-hal-wasm。 2. taskCycle 或 servoCycle 单调递增。 3. activeLine 随 feedback 推进,不停留在 RUN 后第一帧。 4. axisPose 来自同一 tick 的 task/HAL status.ui.axisPose。 5. currentVelocityMmPerMin 来自同一 tick 的 motion currentVel。 6. M428 后 kinsType 切到 tcp-xyzac 或 tcp-xyzbc。 7. M429 后 kinsType 回到 identity。 8. UI 当前高亮行、DRO、runtime feedback 文本读取的是同一个 feedback snapshot。 9. 程序完成后 runState 变为 complete 或 idle,不继续显示 running。 ``` 失败判定: ```text RUN 后 servoCycle 只增加一次,然后 0.5s/2s/5s 不变。 activeLine 固定在某一行不再推进。 axisPose 从 HOME 位姿被 task-local 0 值覆盖。 programRuntimeFeedback.sourceMode 不是 linuxcnc-task-motion-hal-wasm。 RTCP 状态与当前 G-code 的 M428/M429 不一致。 ``` ### 4.5 浏览器端测试 建议扩展: ```text qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs ``` 新增 case: ```text 07-run-preconditions-and-feedback ``` 浏览器断言: ```text 1. 首屏等待 state.iniConfigReadiness.loaded === true。 2. 选择 xyzac-trt,确认 titlebar/profile/INI/kinematics module 都是 xyzac-trt。 3. 点击 POWER、HOME、AUTO。 4. 加载上述短 G-code。 5. 点击 RUN。 6. 采集 t=0.2s、0.5s、1.0s、2.0s、5.0s 状态。 7. 断言 task/servo cycle 单调递增。 8. 断言 UI `.gcode-row.active` 的 data-program-line 等于 state.activeLine。 9. 断言 DRO X/Y/Z/A/C 等于 state.programRuntimeFeedback.axisPose。 10. 断言 canvas dataset 的 RTCP 状态与 state.rtcpState 一致。 ``` 输出证据: ```text qa/web-rtcp-5axis-site-test/output/run-preconditions-feedback.json qa/web-rtcp-5axis-site-test/screenshots/run-preconditions-feedback/*.png ``` ## 5. 编写 RUN 程序的实现步骤 ### Step 1: 增加 `validateRunPreconditions(state)` 位置建议: ```text app/src/state/store.js ``` 职责: ```text 1. 检查 INI 已加载。 2. 检查 profile 与 INI 坐标一致。 3. 检查 profile.kinematicsModuleId 与 kinematics runtime moduleId 一致。 4. 检查 kinematics runtime ready。 5. 检查 task/HAL runtime ready。 6. 检查 machine file staging ready。 7. 检查 taskHalSession.programPath 指向当前 G-code。 ``` 返回结构: ```js { ok: true, profileId: "xyzac-trt", iniPath: ".../xyzac-trt.ini", coordinates: "XYZAC", kinematicsModuleId: "xyzac-trt" } ``` 失败时: ```js { ok: false, operatorMessage: "run blocked: LinuxCNC INI not loaded" } ``` ### Step 2: `RUN` action 先调用前置检查 当前 `RUN` 不能直接进入: ```text runTaskHalCommandSequence([...], { taskCycles: 5 }) ``` 应先: ```text 1. gateLinuxCncTaskAction(state, action) 2. validateRunPreconditions(state) 3. initializeTaskHalSession({ openProgram: true }) 如需要 4. 确认 readStatus() 的 openProgram/programPath 与 state.activeProgram 对齐 ``` 未满足前置条件时,只设置 `operatorMessage`,不推进任何 line/sample。 ### Step 3: 拆分 task command 与执行 pump `RUN` 应只发送 LinuxCNC task 命令: ```text EMC_TASK_SET_STATE ON EMC_TASK_SET_MODE AUTO EMC_TASK_PLAN_RUN line = activeLine - programStartLine ``` 然后启动 feedback pump: ```text while running: taskHalRuntime.runCycles({ taskCycles: batchSize }) status = taskHalRuntime.readStatus() dispatch TASK_HAL_STATUS_APPLIED(status) append feedback history if complete/paused/aborted/stopped: stop pump ``` 说明: ```text 这里的 pump 可以在 store 主线程调度,也可以放到 worker 内部 postMessage 推送。 关键不是 timer 本身,而是每个 tick 都必须由 task/HAL/motion runtime 的 runCycles/readStatus 产生反馈。 ``` ### Step 4: 增加 RUN pump 状态 建议 state 增加: ```js taskHalRunPump: { active: false, sequence: 0, profileId: null, iniPath: null, kinematicsModuleId: null, tickCount: 0, lastStatusAt: null, lastError: null } ``` 同时增加: ```js programRuntimeFeedbackHistory: [] ``` 历史最多保留 100 到 500 条,避免浏览器长程序内存无限增长。 ### Step 5: `TASK_HAL_STATUS_APPLIED` 只消费 status `applyTaskHalStatusPatch()` 应继续负责: ```text activeLine axisPose kinsType rtcpState runState feed.currentVelocity programRuntimeFeedback ``` 但需要补充: ```text 1. programRuntimeFeedbackHistory append 当前 feedback。 2. 记录 feedback.profileId、iniPath、kinematicsModuleId。 3. 对 ui.axisPoseFrame 做严格处理,避免 task-local 0 值覆盖 HOME/work pose。 4. 运行态 axisPose、velocity、DTG、activeLine 必须来自同一个 status snapshot。 ``` ### Step 6: STOP/ABORT/PAUSE/RESUME 控制 pump 行为要求: ```text STOP/ABORT: send EMC_TASK_ABORT stop pump read final status PAUSE: send EMC_TASK_PLAN_PAUSE pump 可停止或降频读取 paused status runState=paused RESUME: send EMC_TASK_PLAN_RESUME restart pump STEP: 不走 fixture sample playback 发送/推进一个明确的 task cycle batch readStatus 一次 runState=stepping 或 paused ``` ### Step 7: UI 显示执行历史 在 `app/src/ui/gmoccapy-shell.js` 的 info/sidebar 区域增加: ```text Last feedback: sourceMode / sample / line / taskCycle / servoCycle / velocity / kinsType Run history: 最近 10 条 line/tick ``` UI 只显示 `programRuntimeFeedbackHistory`,不自行推导行号或坐标。 ### Step 8: 移除真实 RUN 的 fixture fallback 混淆 保留 fallback 可以用于开发,但必须明确标记: ```text programExecutionSourceMode=fixture-line-playback operatorMessage=LinuxCNC task/HAL unavailable; fixture playback only fullLinuxCncProgramExecutionReady=false ``` 在真实 `taskHalRuntime.loaded === true` 且前置条件失败时,不应退回 fixture playback。 ## 6. 开发验收命令 基础检查: ```bash node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_kinematics_runtime.mjs node web-rtcp-5axis-sim-plan/tests/node/verify_machine_file_staging.mjs node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_task_hal_runtime.mjs npm --prefix web-rtcp-5axis-sim-plan/app run build ``` 完整 node smoke: ```bash npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node ``` 浏览器 smoke: ```bash bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh ``` QA 证据采集: ```bash node qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs ``` ## 7. 完成标准 `RUN` 可以判定为完善,必须同时满足: ```text 1. 每次 RUN 都能追溯到明确 INI。 2. 每次 RUN 都能说明机床类型和坐标轴。 3. 每次 RUN 都能说明使用哪个 LinuxCNC 五轴 kinematics module。 4. M428/M429/M430 的 switchkins 状态来自 LinuxCNC remap/HAL/task feedback 链。 5. activeLine、DRO、速度、DTG、程序时间来自同一个 task/HAL/motion feedback snapshot。 6. RUN 后 taskCycle/servoCycle 随时间持续推进,直到 paused/stopped/complete。 7. UI 高亮行和执行历史能复现逐行执行过程。 8. fallback playback 不能冒充 LinuxCNC task/HAL runtime。 ``` 未满足以上条件时,状态应继续标记为: ```text fullLinuxCncProgramExecutionReady=false hardwareDrive=false hostRealtimeKernel=false externalUserMProcessReady=false ```