# 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. INI definition contract `RUN` 只能基于一个完整、可验证的 LinuxCNC INI 上下文启动。INI 不是展示信息,而是机床契约来源。`parseLinuxCncIni()` 和 `applyIniConfigToProfile()` 必须把下面字段转成结构化状态,RUN gate 只读取结构化状态,不在 RUN action 中重新猜测字符串。 ### 3.1 必须存在的 INI sections 当前五轴 `xyzac-trt` / `xyzbc-trt` profile 至少要求这些 section: | section | 是否必填 | RUN gate 用途 | | --- | --- | --- | | `[EMC]` | 必填 | 识别 machine name,确认是 switchkins 五轴 profile | | `[DISPLAY]` | 必填 | 提供默认打开程序、程序目录、UI 坐标提示;不能作为运动学真值 | | `[RS274NGC]` | 必填 | 提供 remap、subroutine path、HAL pin vars、parameter file | | `[KINS]` | 必填 | 提供 kinematics module 和 joint 数,是 kinematics/runtime 匹配依据 | | `[HAL]` | 必填 | 提供 HAL 文件、HALUI、POSTGUI_HALFILE、switchkins HALCMD | | `[HALUI]` | 必填 | 提供 M428/M429/M430 operator command 顺序和语义提示 | | `[TRAJ]` | 必填 | 提供 COORDINATES、单位、速度/加速度上限 | | `[EMCMOT]` | 必填 | 提供 motmod、SERVO_PERIOD、COMM_TIMEOUT | | `[TASK]` | 必填 | 提供 task module 和 CYCLE_TIME | | `[EMCIO]` | 必填 | 提供 tool table | | `[AXIS_*]` | 必填 | 提供各坐标轴 limit/velocity/acceleration | | `[JOINT_*]` | 必填 | 提供 joint 类型、home、limit、home sequence | 不满足这些 section 时,INI readiness 必须是 not ready,RUN 必须阻断。 ### 3.2 必填字段与 gate 用途 `[EMC]`: ```text VERSION MACHINE ``` 要求: ```text xyzac-trt: MACHINE = sim-xyzac-trt-kins (switchkins) xyzbc-trt: MACHINE = sim-xyzbc-trt-kins (switchkins) ``` 用途: ```text 1. 推导 profileId。 2. 确认当前 profile 是 switchkins 五轴仿真机床。 3. 作为 task/HAL session metadata,写入 feedback 证据。 ``` `[DISPLAY]`: ```text GEOMETRY OPEN_FILE JOG_AXES DISPLAY PROGRAM_PREFIX POSITION_OFFSET POSITION_FEEDBACK MAX_LINEAR_VELOCITY MAX_ANGULAR_VELOCITY ``` 用途: ```text 1. OPEN_FILE 只能作为默认程序候选,RUN 实际程序必须来自 staged selected G-code。 2. PROGRAM_PREFIX 用于解析 machine-file staging 的相对路径。 3. GEOMETRY/JOG_AXES 用于 UI 呈现,不得覆盖 [TRAJ] COORDINATES。 ``` `[RS274NGC]`: ```text SUBROUTINE_PATH HAL_PIN_VARS = 1 REMAP = M428 modalgroup=10 ngc=428remap REMAP = M429 modalgroup=10 ngc=429remap REMAP = M430 modalgroup=10 ngc=430remap PARAMETER_FILE ``` 用途: ```text 1. HAL_PIN_VARS=1 是 remap 子程序读取 motion.switchkins-type 的前提。 2. M428/M429/M430 remap 文件必须被 stage 到同一 machine context。 3. PARAMETER_FILE 必须随 profile 区分,不能 xyzac/xyzbc 混用。 ``` `[KINS]`: ```text KINEMATICS JOINTS ``` 要求: ```text xyzac-trt: KINEMATICS = xyzac-trt-kins sparm=identityfirst xyzbc-trt: KINEMATICS = xyzbc-trt-kins sparm=identityfirst JOINTS = 5 ``` 用途: ```text 1. 从 KINEMATICS 推导 kinematicsModuleId。 2. 校验 kinematics WASM moduleId。 3. 校验 joint count 与 [JOINT_0]...[JOINT_4] 完整性。 4. sparm=identityfirst 决定 switchkins type 0 是 identity,不能按默认 kins 猜测。 ``` `[HAL]`: ```text HALUI = halui HALFILE = LIB:basic_sim.tcl POSTGUI_HALFILE = switchkins_postgui.hal HALCMD = net :kinstype-select <= motion.analog-out-03 => motion.switchkins-type ``` 用途: ```text 1. HALUI 存在时,UI command 与 LinuxCNC halui 行为对齐。 2. HALFILE/POSTGUI_HALFILE 必须 stage,task/HAL runtime 才能构造同一机床上下文。 3. switchkins HALCMD 是 M428/M429/M430 影响 motion.switchkins-type 的证据。 ``` `[HALUI]`: ```text MDI_COMMAND = M429 MDI_COMMAND = M428 MDI_COMMAND = M430 ``` 用途: ```text 1. 记录 operator 命令入口。 2. 与 [RS274NGC] REMAP 和 [HAL] switchkins HALCMD 交叉校验。 3. 不直接作为 RUN 自动插入命令;RUN 程序内的 M428/M429/M430 由 interpreter/remap 处理。 ``` `[TRAJ]`: ```text COORDINATES LINEAR_UNITS ANGULAR_UNITS DEFAULT_LINEAR_VELOCITY MAX_LINEAR_VELOCITY MAX_LINEAR_ACCELERATION DEFAULT_LINEAR_ACCELERATION ``` 要求: ```text xyzac-trt: COORDINATES = XYZAC xyzbc-trt: COORDINATES = XYZBC LINEAR_UNITS = mm ANGULAR_UNITS = deg ``` 用途: ```text 1. COORDINATES 是 UI 轴、G-code 轴、runtime axisPose 的主契约。 2. 单位决定 DRO 和 velocity 显示,不能由前端默认值覆盖。 3. velocity/acceleration 是 planner/runtime limit 的来源。 ``` `[EMCMOT]` 与 `[TASK]`: ```text [EMCMOT] EMCMOT = motmod SERVO_PERIOD = 1000000 COMM_TIMEOUT = 1 [TASK] TASK = milltask CYCLE_TIME = 0.010 ``` 用途: ```text 1. SERVO_PERIOD 用于 taskHalStatusLoop 的 servo tick 语义。 2. CYCLE_TIME 用于 task cycle 语义和测试断言。 3. runtime readiness 必须能报告 task/motion/HAL 三者已按这些参数初始化。 ``` `[EMCIO]`: ```text TOOL_TABLE ``` 用途: ```text 1. tool table 必须 stage 到 machine files。 2. G43/tool offset 相关状态不能用空表静默替代。 ``` `[AXIS_*]`: ```text MIN_LIMIT MAX_LIMIT MAX_VELOCITY MAX_ACCELERATION ``` 要求: ```text xyzac-trt: [AXIS_X] [AXIS_Y] [AXIS_Z] [AXIS_A] [AXIS_C] xyzbc-trt: [AXIS_X] [AXIS_Y] [AXIS_Z] [AXIS_B] [AXIS_C] ``` 用途: ```text 1. 校验 profile coordinates 对应的 axis section 完整。 2. 约束 UI limit、DRO 范围、preview bounds。 3. 防止 xyzac/xyzbc 轴表混用。 ``` `[JOINT_*]`: ```text TYPE HOME MIN_LIMIT MAX_LIMIT MAX_VELOCITY MAX_ACCELERATION HOME_SEARCH_VEL HOME_SEQUENCE ``` 要求: ```text JOINT_0..JOINT_4 必须完整。 JOINT_0..JOINT_2 TYPE = LINEAR。 xyzac-trt: JOINT_3/JOINT_4 分别对应 A/C rotary。 xyzbc-trt: JOINT_3/JOINT_4 分别对应 B/C rotary。 ``` 用途: ```text 1. 校验 [KINS] JOINTS=5 与 joint section 数量一致。 2. home/allHomed gate 的配置来源。 3. task/HAL runtime 初始化 joint status 的来源。 ``` ### 3.3 INI readiness 输出 `parseLinuxCncIni()` 应输出或间接形成这些结构化字段: ```js { profileId: "xyzac-trt", iniPath: "configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", machineName: "sim-xyzac-trt-kins (switchkins)", traj: { coordinates: "XYZAC", linearUnits: "mm", angularUnits: "deg" }, kinematics: { name: "xyzac-trt-kins", moduleId: "xyzac-trt", switchkins: true, identityFirst: true, joints: 5 }, rs274ngc: { halPinVars: true, remaps: ["M428", "M429", "M430"], subroutinePath: "./remap_subs", parameterFile: "xyzac.var" }, hal: { halui: "halui", halFiles: ["LIB:basic_sim.tcl"], postguiHalFiles: ["switchkins_postgui.hal"], switchkinsSignal: "motion.analog-out-03 => motion.switchkins-type" }, emcmot: { module: "motmod", servoPeriodNs: 1000000 }, task: { module: "milltask", cycleTimeSeconds: 0.010 }, emcio: { toolTable: "xyzac-trt.tbl" }, axes: { X: { minLimit: -200, maxLimit: 200 }, Y: { minLimit: -100, maxLimit: 100 }, Z: { minLimit: -120, maxLimit: 120 }, A: { minLimit: -100, maxLimit: 50 }, C: { minLimit: -36000, maxLimit: 36000 } }, joints: [ { index: 0, type: "LINEAR", axis: "X" }, { index: 1, type: "LINEAR", axis: "Y" }, { index: 2, type: "LINEAR", axis: "Z" }, { index: 3, type: "ANGULAR", axis: "A" }, { index: 4, type: "ANGULAR", axis: "C" } ], validation: { ready: true, errors: [] } } ``` `xyzbc-trt` 的输出必须只在 profileId、machineName、coordinates、kinematics module、parameter/tool table、rotary axis B/C 等字段上变化,不能沿用 xyzac 的 A/C axis contract。 ### 3.4 INI contract 失败时的阻断 这些情况必须让 `state.iniConfigReadiness.ready=false`,并阻断 RUN: ```text 缺少 [EMC] MACHINE。 缺少 [TRAJ] COORDINATES。 COORDINATES 不是 XYZAC 或 XYZBC。 [KINS] KINEMATICS 不是 xyzac-trt-kins 或 xyzbc-trt-kins。 [KINS] JOINTS 不是 5。 缺少 M428/M429/M430 remap。 HAL_PIN_VARS 不是 1。 缺少 motion.analog-out-03 => motion.switchkins-type HALCMD。 缺少 [EMCMOT] SERVO_PERIOD。 缺少 [TASK] CYCLE_TIME。 缺少 TOOL_TABLE。 axis section 与 COORDINATES 不一致。 joint section 数量与 [KINS] JOINTS 不一致。 xyzac INI 指向 xyzbc tool table/parameter file,或反向混用。 ``` 阻断消息建议: ```text run blocked: LinuxCNC INI contract invalid run blocked: INI missing required section [KINS] run blocked: INI coordinates do not match selected profile run blocked: INI switchkins remap incomplete run blocked: INI HAL switchkins signal missing run blocked: INI joint/axis contract mismatch ``` ### 3.5 与 RUN gate 的关系 INI contract 通过只是 RUN 的第一层 gate。RUN 仍必须继续检查: ```text 1. kinematics WASM 已按 INI [KINS] moduleId 加载。 2. machine files 已按同一个 INI stage。 3. task/HAL runtime 已按同一个 INI/session 初始化。 4. selected G-code 已通过 task plan open 打开。 5. task status 的 opened file 与 selected staged G-code 一致。 6. machine ON、AUTO、homed/no_force_homing 满足。 ``` 所以不能把 `parseLinuxCncIni().validation.ready=true` 等同于 `RUN` ready。 ## 4. 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 state.taskHalSession.openProgram === true state.taskHalStatus.task.file 或 openedProgram.path 指向同一个 staged G-code state.taskHalStatus.task.state 是 ON state.taskHalStatus.task.mode 是 AUTO,或 RUN 前可切换到 AUTO state.taskHalStatus.motion.allHomed === true,或当前仿真 profile 明确 no_force_homing ``` 如果任一项不满足,`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 run blocked: machine is not on run blocked: machine is not homed run blocked: task mode is not AUTO ``` ## 5. LinuxCNC 源码对照结论 `RUN` 命令修改方向总体合适,但必须严格贴近 LinuxCNC 的 task 语义: ```text RUN 不是前端 sample playback。 RUN 不是直接解析 G-code 后自增 activeLine。 RUN 是在已打开程序、机床可运行、AUTO 模式下发送 EMC_TASK_PLAN_RUN, 随后由 task 主循环持续 read/execute interpreter,并从 motion/HAL 状态读取反馈。 ``` 源码证据: ```text /home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/halui.cc:1096 sendProgramRun(int line) - updateStatus() - 如果 emcStatus->task.file 为空,直接返回 -1 - 保存 programStartLine - 设置 EMC_TASK_PLAN_RUN.line - sendAuto() - emcCommandSend(EMC_TASK_PLAN_RUN) /home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/shcom.cc:750 sendProgramOpen(program) - 发送 EMC_TASK_PLAN_OPEN - 本地进程发送文件名 - remote process 通过 remote_buffer 分块发送文件内容 /home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/shcom.cc:814 sendProgramRun(int line) - status 为 AUTO update 时先 updateStatus() - task.file 为空时尝试重新打开 lastProgramFile - 再发送 EMC_TASK_PLAN_RUN /home/meswork/cnc_wams/linuxcnc/src/emc/task/emctaskmain.cc:2164 EMC_TASK_PLAN_OPEN - 接收 remote 文件或打开本地文件 - 调用 emcTaskPlanOpen(open_msg->file) - 成功后写入 emcStatus->task.file /home/meswork/cnc_wams/linuxcnc/src/emc/task/emctaskmain.cc:2318 EMC_TASK_PLAN_RUN - 未 homed 且 no_force_homing=false 时拒绝运行 - 清 single stepping - 必要时重新 emcTaskPlanOpen(emcStatus->task.file) - 保存 programStartLine - 设置 interpState=READING - 清 task_paused /home/meswork/cnc_wams/linuxcnc/src/emc/task/emctaskmain.cc:2337 EMC_TASK_PLAN_PAUSE - emcTrajPause() - interpState=PAUSED - task_paused=1 /home/meswork/cnc_wams/linuxcnc/src/emc/task/emctaskmain.cc:2369 EMC_TASK_PLAN_RESUME - emcTrajResume() - 恢复 interpState - 清 task_paused /home/meswork/cnc_wams/linuxcnc/src/emc/task/emctask.cc:543 emcTaskPlanOpen(file) - 清 motionLine/currentLine/readLine - interp.open(file) - taskplanopen=1 /home/meswork/cnc_wams/linuxcnc/src/emc/task/emctask.cc:566 emcTaskPlanRead() - interp.read() - 文件未打开时用 emcStatus->task.file 重新 open/read ``` 对当前 web runtime 的约束: ```text 1. initializeTaskHalSession({ openProgram: true }) 必须等价于先完成 EMC_TASK_PLAN_OPEN。 2. RUN gate 必须确认 opened program 与当前 selected G-code 是同一个 staged machine-file。 3. RUN 发送 EMC_TASK_PLAN_RUN 后,UI 不能用 canonical timing 或 fixture sample 冒充 task 反馈。 4. 连续反馈必须来自 taskHalRuntime.runCycles/readStatus,且 status 的 task/motion/HAL 字段必须同源。 5. PAUSE/RESUME/ABORT 要按 LinuxCNC task command 修改状态循环,而不是只改前端 runState。 ``` ## 6. 详细测试计划 ### 6.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 ``` ### 6.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。 ### 6.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 ``` ### 6.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 不一致。 ``` ### 6.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 ``` ## 7. 编写 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。 8. 检查 task status 的 opened file 与当前 staged G-code 一致。 9. 检查 machine ON、AUTO mode、homed/no_force_homing。 ``` 返回结构: ```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 对齐 5. 如果 task mode 不是 AUTO,先发送 EMC_TASK_SET_MODE AUTO 并等待 status 确认 6. 如果 task state 不是 ON,先发送 EMC_TASK_SET_STATE ON 并等待 status 确认 ``` 未满足前置条件时,只设置 `operatorMessage`,不推进任何 line/sample。 ### Step 3: 拆分 task command 与 taskHalStatusLoop `RUN` 应只发送 LinuxCNC task 命令: ```text EMC_TASK_SET_STATE ON EMC_TASK_SET_MODE AUTO EMC_TASK_PLAN_RUN line = linuxcncStartLine ``` 说明: ```text linuxcncStartLine 应按 LinuxCNC 的 program run line 语义保存。 普通从头运行传 0。 run-from-line 才传明确 line,并需要单独处理 start line、previous modal state 和安全性。 不要用 UI activeLine 自行推导 programStartLine。 ``` 然后启动 `taskHalStatusLoop`: ```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 taskHalStatusLoop ``` 说明: ```text 这里的 taskHalStatusLoop 可以在 store 主线程调度,也可以放到 worker 内部 postMessage 推送。 关键不是 timer 本身,而是每个 tick 都必须由 task/HAL/motion runtime 的 runCycles/readStatus 产生反馈。 ``` ### Step 4: 增加 RUN status loop 状态 建议 state 增加: ```js taskHalStatusLoop: { 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 控制 taskHalStatusLoop 行为要求: ```text STOP/ABORT: send EMC_TASK_ABORT stop taskHalStatusLoop read final status PAUSE: send EMC_TASK_PLAN_PAUSE taskHalStatusLoop 可停止或降频读取 paused status runState=paused RESUME: send EMC_TASK_PLAN_RESUME restart taskHalStatusLoop 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。 ## 8. 开发验收命令 基础检查: ```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 node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.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 ``` ## 9. 完成标准 `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。 9. RUN 失败时能明确区分 INI/profile、open program、machine ON、AUTO、homed、runtime readiness 哪一项未满足。 ``` 未满足以上条件时,状态应继续标记为: ```text fullLinuxCncProgramExecutionReady=false hardwareDrive=false hostRealtimeKernel=false externalUserMProcessReady=false ```