Files
cnc_wams/web-rtcp-5axis-sim-plan/working_run/02-run-preconditions-test-and-implementation-steps.md

28 KiB
Raw Blame History

02 RUN 前置条件、详细测试与编写步骤

生成时间2026-06-22

1. 核心原则

RUN 不是孤立按钮逻辑。对五轴机床,必须先确定 LinuxCNC 机床上下文,再执行程序。

正确顺序是:

明确 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

源码落点:

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

必须确认:

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 映射:

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

必须确认:

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 映射:

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 readyRUN 必须阻断。

3.2 必填字段与 gate 用途

[EMC]

VERSION
MACHINE

要求:

xyzac-trt: MACHINE = sim-xyzac-trt-kins (switchkins)
xyzbc-trt: MACHINE = sim-xyzbc-trt-kins (switchkins)

用途:

1. 推导 profileId。
2. 确认当前 profile 是 switchkins 五轴仿真机床。
3. 作为 task/HAL session metadata写入 feedback 证据。

[DISPLAY]

GEOMETRY
OPEN_FILE
JOG_AXES
DISPLAY
PROGRAM_PREFIX
POSITION_OFFSET
POSITION_FEEDBACK
MAX_LINEAR_VELOCITY
MAX_ANGULAR_VELOCITY

用途:

1. OPEN_FILE 只能作为默认程序候选RUN 实际程序必须来自 staged selected G-code。
2. PROGRAM_PREFIX 用于解析 machine-file staging 的相对路径。
3. GEOMETRY/JOG_AXES 用于 UI 呈现,不得覆盖 [TRAJ] COORDINATES。

[RS274NGC]

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

用途:

1. HAL_PIN_VARS=1 是 remap 子程序读取 motion.switchkins-type 的前提。
2. M428/M429/M430 remap 文件必须被 stage 到同一 machine context。
3. PARAMETER_FILE 必须随 profile 区分,不能 xyzac/xyzbc 混用。

[KINS]

KINEMATICS
JOINTS

要求:

xyzac-trt: KINEMATICS = xyzac-trt-kins sparm=identityfirst
xyzbc-trt: KINEMATICS = xyzbc-trt-kins sparm=identityfirst
JOINTS = 5

用途:

1. 从 KINEMATICS 推导 kinematicsModuleId。
2. 校验 kinematics WASM moduleId。
3. 校验 joint count 与 [JOINT_0]...[JOINT_4] 完整性。
4. sparm=identityfirst 决定 switchkins type 0 是 identity不能按默认 kins 猜测。

[HAL]

HALUI = halui
HALFILE = LIB:basic_sim.tcl
POSTGUI_HALFILE = switchkins_postgui.hal
HALCMD = net :kinstype-select <= motion.analog-out-03 => motion.switchkins-type

用途:

1. HALUI 存在时UI command 与 LinuxCNC halui 行为对齐。
2. HALFILE/POSTGUI_HALFILE 必须 stagetask/HAL runtime 才能构造同一机床上下文。
3. switchkins HALCMD 是 M428/M429/M430 影响 motion.switchkins-type 的证据。

[HALUI]

MDI_COMMAND = M429
MDI_COMMAND = M428
MDI_COMMAND = M430

用途:

1. 记录 operator 命令入口。
2. 与 [RS274NGC] REMAP 和 [HAL] switchkins HALCMD 交叉校验。
3. 不直接作为 RUN 自动插入命令RUN 程序内的 M428/M429/M430 由 interpreter/remap 处理。

[TRAJ]

COORDINATES
LINEAR_UNITS
ANGULAR_UNITS
DEFAULT_LINEAR_VELOCITY
MAX_LINEAR_VELOCITY
MAX_LINEAR_ACCELERATION
DEFAULT_LINEAR_ACCELERATION

要求:

xyzac-trt: COORDINATES = XYZAC
xyzbc-trt: COORDINATES = XYZBC
LINEAR_UNITS = mm
ANGULAR_UNITS = deg

用途:

1. COORDINATES 是 UI 轴、G-code 轴、runtime axisPose 的主契约。
2. 单位决定 DRO 和 velocity 显示,不能由前端默认值覆盖。
3. velocity/acceleration 是 planner/runtime limit 的来源。

[EMCMOT][TASK]

[EMCMOT]
EMCMOT = motmod
SERVO_PERIOD = 1000000
COMM_TIMEOUT = 1

[TASK]
TASK = milltask
CYCLE_TIME = 0.010

用途:

1. SERVO_PERIOD 用于 taskHalStatusLoop 的 servo tick 语义。
2. CYCLE_TIME 用于 task cycle 语义和测试断言。
3. runtime readiness 必须能报告 task/motion/HAL 三者已按这些参数初始化。

[EMCIO]

TOOL_TABLE

用途:

1. tool table 必须 stage 到 machine files。
2. G43/tool offset 相关状态不能用空表静默替代。

[AXIS_*]

MIN_LIMIT
MAX_LIMIT
MAX_VELOCITY
MAX_ACCELERATION

要求:

xyzac-trt: [AXIS_X] [AXIS_Y] [AXIS_Z] [AXIS_A] [AXIS_C]
xyzbc-trt: [AXIS_X] [AXIS_Y] [AXIS_Z] [AXIS_B] [AXIS_C]

用途:

1. 校验 profile coordinates 对应的 axis section 完整。
2. 约束 UI limit、DRO 范围、preview bounds。
3. 防止 xyzac/xyzbc 轴表混用。

[JOINT_*]

TYPE
HOME
MIN_LIMIT
MAX_LIMIT
MAX_VELOCITY
MAX_ACCELERATION
HOME_SEARCH_VEL
HOME_SEQUENCE

要求:

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。

用途:

1. 校验 [KINS] JOINTS=5 与 joint section 数量一致。
2. home/allHomed gate 的配置来源。
3. task/HAL runtime 初始化 joint status 的来源。

3.3 INI readiness 输出

parseLinuxCncIni() 应输出或间接形成这些结构化字段:

{
  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

缺少 [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或反向混用。

阻断消息建议:

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 仍必须继续检查:

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 进入真正执行前,应要求这些状态全部明确:

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 后伪装成真实运行。

建议阻断消息:

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 语义:

RUN 不是前端 sample playback。
RUN 不是直接解析 G-code 后自增 activeLine。
RUN 是在已打开程序、机床可运行、AUTO 模式下发送 EMC_TASK_PLAN_RUN
随后由 task 主循环持续 read/execute interpreter并从 motion/HAL 状态读取反馈。

源码证据:

/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 的约束:

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。

测试文件建议:

tests/node/verify_run_preconditions.mjs

断言:

1. 读取 xyzac-trt.iniparseLinuxCncIni() 返回:
   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.iniparseLinuxCncIni() 返回:
   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

命令:

node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs

通过标准:

run_preconditions_ini_profile_smoke=ok

6.2 运动学模块确认测试

目标:证明当前机床的五轴运动学算法已经按 INI/profile 加载。

断言:

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=1frame source 仍是 linuxcnc_kinematics_wasm_c_abi。

6. 对 xyzbc-trt 重复同样测试。

已有相关测试:

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 机床目录上下文。

断言:

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

已有相关测试:

node web-rtcp-5axis-sim-plan/tests/node/verify_machine_file_staging.mjs

6.4 RUN 连续 feedback 测试

目标:证明点击 RUN 后,不是只推进一次 taskCycles=5,而是持续从 task/HAL/motion 获取反馈。

测试程序:

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 轴:

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断言

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。

失败判定:

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 浏览器端测试

建议扩展:

qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs

新增 case

07-run-preconditions-and-feedback

浏览器断言:

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 一致。

输出证据:

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)

位置建议:

app/src/state/store.js

职责:

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。

返回结构:

{
  ok: true,
  profileId: "xyzac-trt",
  iniPath: ".../xyzac-trt.ini",
  coordinates: "XYZAC",
  kinematicsModuleId: "xyzac-trt"
}

失败时:

{
  ok: false,
  operatorMessage: "run blocked: LinuxCNC INI not loaded"
}

Step 2: RUN action 先调用前置检查

当前 RUN 不能直接进入:

runTaskHalCommandSequence([...], { taskCycles: 5 })

应先:

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 命令:

EMC_TASK_SET_STATE ON
EMC_TASK_SET_MODE AUTO
EMC_TASK_PLAN_RUN line = linuxcncStartLine

说明:

linuxcncStartLine 应按 LinuxCNC 的 program run line 语义保存。
普通从头运行传 0。
run-from-line 才传明确 line并需要单独处理 start line、previous modal state 和安全性。
不要用 UI activeLine 自行推导 programStartLine。

然后启动 taskHalStatusLoop

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

说明:

这里的 taskHalStatusLoop 可以在 store 主线程调度,也可以放到 worker 内部 postMessage 推送。
关键不是 timer 本身,而是每个 tick 都必须由 task/HAL/motion runtime 的 runCycles/readStatus 产生反馈。

Step 4: 增加 RUN status loop 状态

建议 state 增加:

taskHalStatusLoop: {
  active: false,
  sequence: 0,
  profileId: null,
  iniPath: null,
  kinematicsModuleId: null,
  tickCount: 0,
  lastStatusAt: null,
  lastError: null
}

同时增加:

programRuntimeFeedbackHistory: []

历史最多保留 100 到 500 条,避免浏览器长程序内存无限增长。

Step 5: TASK_HAL_STATUS_APPLIED 只消费 status

applyTaskHalStatusPatch() 应继续负责:

activeLine
axisPose
kinsType
rtcpState
runState
feed.currentVelocity
programRuntimeFeedback

但需要补充:

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

行为要求:

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 区域增加:

Last feedback:
  sourceMode / sample / line / taskCycle / servoCycle / velocity / kinsType

Run history:
  最近 10 条 line/tick

UI 只显示 programRuntimeFeedbackHistory,不自行推导行号或坐标。

Step 8: 移除真实 RUN 的 fixture fallback 混淆

保留 fallback 可以用于开发,但必须明确标记:

programExecutionSourceMode=fixture-line-playback
operatorMessage=LinuxCNC task/HAL unavailable; fixture playback only
fullLinuxCncProgramExecutionReady=false

在真实 taskHalRuntime.loaded === true 且前置条件失败时,不应退回 fixture playback。

8. 开发验收命令

基础检查:

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

npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node

浏览器 smoke

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 证据采集:

node qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs

9. 完成标准

RUN 可以判定为完善,必须同时满足:

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 哪一项未满足。

未满足以上条件时,状态应继续标记为:

fullLinuxCncProgramExecutionReady=false
hardwareDrive=false
hostRealtimeKernel=false
externalUserMProcessReady=false