1036 lines
26 KiB
Markdown
1036 lines
26 KiB
Markdown
# 05 RUN 功能程序实现详细实施步骤
|
||
|
||
生成时间:2026-06-22
|
||
|
||
## 1. 实施目标
|
||
|
||
本文件把 `working_run` 下 01-04 文档中的原则、前置条件、源码对照和测试结论,整理成可直接执行的程序实现步骤。
|
||
|
||
目标不是做一个前端 G-code 播放器,而是实现这个链路:
|
||
|
||
```text
|
||
LinuxCNC INI contract
|
||
-> profile 与 INI 一致
|
||
-> kinematics WASM 与 INI [KINS] 一致
|
||
-> machine files 按同一个 INI stage
|
||
-> task/HAL runtime 按同一个 machine context 初始化
|
||
-> selected G-code 通过 task plan open 打开
|
||
-> RUN 发送 LinuxCNC task command
|
||
-> taskHalStatusLoop 持续 runCycles/readStatus
|
||
-> UI 只消费 task/HAL/motion feedback
|
||
```
|
||
|
||
## 2. 实施边界
|
||
|
||
必须保持的边界:
|
||
|
||
```text
|
||
1. 不在 UI/store 自行解析 G-code 来推进 activeLine。
|
||
2. 不在 UI/store 自造 axisPose、velocity、DTG。
|
||
3. 不把 canonical timing estimate 当成真实 task/HAL runtime。
|
||
4. 不在真实 RUN 前置失败时退回 fixture playback。
|
||
5. RUN、PAUSE、RESUME、STOP 必须映射为 LinuxCNC task command 语义。
|
||
6. activeLine、DRO、RTCP、velocity、runtime feedback 必须来自同一个 task/HAL/motion status snapshot。
|
||
```
|
||
|
||
允许保留的 fallback:
|
||
|
||
```text
|
||
fixture-line-playback 只能作为开发演示 fallback。
|
||
一旦 taskHalRuntime.loaded === true,真实 RUN 失败必须明确 blocked,不能 fallback 冒充 LinuxCNC runtime。
|
||
```
|
||
|
||
## 3. 主要代码落点
|
||
|
||
需要实现或调整的文件:
|
||
|
||
```text
|
||
app/src/runtime/linuxcnc-ini-runtime.js
|
||
app/src/profiles/index.js
|
||
app/src/profiles/xyzac-trt.js
|
||
app/src/profiles/xyzbc-trt.js
|
||
app/src/runtime/linuxcnc-kinematics-runtime.js
|
||
app/src/runtime/linuxcnc-machine-file-staging.js
|
||
app/src/runtime/linuxcnc-task-hal-runtime.js
|
||
app/src/runtime/linuxcnc-task-hal-worker-client.js
|
||
app/src/runtime/linuxcnc-task-hal-worker.js
|
||
app/src/state/store.js
|
||
app/src/state/linuxcnc-task-policy.js
|
||
app/src/runtime/full-execution-boundary.js
|
||
app/src/ui/gmoccapy-shell.js
|
||
app/package.json
|
||
```
|
||
|
||
需要新增或扩展的测试:
|
||
|
||
```text
|
||
tests/node/verify_run_preconditions.mjs
|
||
tests/node/verify_machine_file_staging.mjs
|
||
tests/node/verify_linuxcnc_ini_runtime.mjs
|
||
tests/node/verify_linuxcnc_kinematics_runtime.mjs
|
||
tests/node/verify_linuxcnc_task_hal_runtime.mjs
|
||
tests/node/verify_run_feedback_loop.mjs
|
||
tests/node/verify_rtcp_store.mjs
|
||
qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs
|
||
```
|
||
|
||
## 4. 阶段 0:建立当前状态基线
|
||
|
||
先确认当前代码和文档一致,避免在错误前提上继续实现。
|
||
|
||
执行:
|
||
|
||
```bash
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.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
|
||
```
|
||
|
||
基线记录:
|
||
|
||
```text
|
||
1. 哪些测试已通过。
|
||
2. 哪些测试失败,失败是否来自外部 linuxcnc source 缺文件、runtime 缺功能,还是 RUN 逻辑本身。
|
||
3. 当前 store 中是否已有 validateRunPreconditions、initializeTaskHalSession、runValidatedTaskHalProgramRun、taskHalStatusLoop。
|
||
```
|
||
|
||
通过标准:
|
||
|
||
```text
|
||
已知 smoke 能稳定复现。
|
||
失败项有明确归因。
|
||
不在没有基线的情况下改 RUN 主流程。
|
||
```
|
||
|
||
## 5. 阶段 1:实现 INI definition contract
|
||
|
||
目标:让 `RUN` 依赖结构化 INI readiness,而不是散落字符串和 profile 默认值。
|
||
|
||
### 5.1 扩展 INI parser
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/runtime/linuxcnc-ini-runtime.js
|
||
```
|
||
|
||
实现内容:
|
||
|
||
```text
|
||
1. parseLinuxCncIni(text, options) 必须解析这些 section:
|
||
[EMC]
|
||
[DISPLAY]
|
||
[RS274NGC]
|
||
[KINS]
|
||
[HAL]
|
||
[HALUI]
|
||
[TRAJ]
|
||
[EMCMOT]
|
||
[TASK]
|
||
[EMCIO]
|
||
[AXIS_*]
|
||
[JOINT_*]
|
||
|
||
2. 返回结构必须包含:
|
||
machineName
|
||
profileId
|
||
kinematics.name
|
||
kinematicsModuleId
|
||
kinematicsParameters.sparm
|
||
kinematicsParameters.joints
|
||
traj.coordinates
|
||
traj.linearUnits
|
||
traj.angularUnits
|
||
rs274ngc.halPinVars
|
||
rs274ngc.remaps
|
||
rs274ngc.subroutinePath
|
||
rs274ngc.parameterFile
|
||
hal.halui
|
||
hal.halFiles
|
||
hal.postguiHalFiles
|
||
hal.halcmd
|
||
hal.switchkinsSignal
|
||
halui.mdiCommands
|
||
emcmot.module
|
||
emcmot.servoPeriodNs
|
||
task.module
|
||
task.cycleTimeSeconds
|
||
emcio.toolTable
|
||
axisLimits
|
||
jointConfig
|
||
validation.ready
|
||
validation.errors
|
||
|
||
3. validateIniConfig() 必须校验:
|
||
required sections 存在
|
||
COORDINATES 是 XYZAC 或 XYZBC
|
||
KINEMATICS 与 profile 一致
|
||
JOINTS = 5
|
||
JOINT_0..JOINT_4 完整
|
||
AXIS_* 与 COORDINATES 一致
|
||
M428/M429/M430 remap 完整
|
||
HAL_PIN_VARS = 1
|
||
motion.analog-out-03 => motion.switchkins-type HALCMD 存在
|
||
SERVO_PERIOD 存在
|
||
CYCLE_TIME 存在
|
||
TOOL_TABLE 存在
|
||
```
|
||
|
||
注意:
|
||
|
||
```text
|
||
[DISPLAY] GEOMETRY 和 JOG_AXES 只能用于 UI 显示,不能覆盖 [TRAJ] COORDINATES。
|
||
[KINS] sparm=identityfirst 决定 switchkins type 0 是 identity。
|
||
xyzac-trt 和 xyzbc-trt 的 axis/joint contract 不能混用。
|
||
```
|
||
|
||
### 5.2 应用 INI 到 profile
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/runtime/linuxcnc-ini-runtime.js
|
||
app/src/profiles/index.js
|
||
app/src/profiles/xyzac-trt.js
|
||
app/src/profiles/xyzbc-trt.js
|
||
```
|
||
|
||
实现内容:
|
||
|
||
```text
|
||
1. applyIniConfigToProfile(profile, iniConfig) 只接受 validation.ready=true 的 INI。
|
||
2. profile.machineName 来自 [EMC] MACHINE。
|
||
3. profile.traj 来自 [TRAJ]。
|
||
4. profile.kinematicsModuleId 来自 [KINS] KINEMATICS 推导结果。
|
||
5. profile.axisLimits 来自 [AXIS_*]。
|
||
6. profile.jointConfig 来自 [JOINT_*]。
|
||
7. profile.rs274ngc 来自 [RS274NGC]。
|
||
8. profile.hal 来自 [HAL]。
|
||
9. profile.emcmot/task/emcio 保存到 profile 或 linuxCncIniConfig,供 runtime/session 使用。
|
||
```
|
||
|
||
阻断行为:
|
||
|
||
```text
|
||
INI validation 不 ready 时:
|
||
state.iniConfigReadiness.ready=false
|
||
operatorMessage=LinuxCNC INI contract invalid
|
||
RUN gate 必须返回 run blocked: LinuxCNC INI contract invalid
|
||
```
|
||
|
||
### 5.3 测试
|
||
|
||
扩展:
|
||
|
||
```text
|
||
tests/node/verify_linuxcnc_ini_runtime.mjs
|
||
tests/node/verify_run_preconditions.mjs
|
||
```
|
||
|
||
断言:
|
||
|
||
```text
|
||
1. xyzac-trt.ini 解析出 XYZAC、xyzac-trt-kins、JOINTS=5、AXIS_X/Y/Z/A/C。
|
||
2. xyzbc-trt.ini 解析出 XYZBC、xyzbc-trt-kins、JOINTS=5、AXIS_X/Y/Z/B/C。
|
||
3. M428/M429/M430 remap 完整。
|
||
4. HAL switchkins signal 完整。
|
||
5. 删除任一必填 section 后 validation.ready=false。
|
||
6. 删除 HAL_PIN_VARS 后 RUN 被阻断。
|
||
7. xyzac 使用 xyzbc tool table 或 parameter file 时 validation.ready=false。
|
||
```
|
||
|
||
## 6. 阶段 2:绑定 kinematics runtime
|
||
|
||
目标:运动学 runtime 必须来自 INI [KINS],不能只靠当前 UI profile 猜测。
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/runtime/linuxcnc-kinematics-runtime.js
|
||
wasm-port/runtime/sdk/src/linuxcnc-kinematics.js
|
||
app/src/state/store.js
|
||
```
|
||
|
||
实现内容:
|
||
|
||
```text
|
||
1. load kinematics runtime 时只接受 iniConfig.kinematicsModuleId。
|
||
2. state.kinematicsRuntimeReadiness.moduleId 必须等于 profile.kinematicsModuleId。
|
||
3. switchKinematics(type) 后 readback 的 switchkinsType 必须进入 rtcpFrame metadata。
|
||
4. rtcpFrame.sourceMode 必须是 source-derived-kinematics-wasm。
|
||
5. frame metadata 必须记录:
|
||
profileId
|
||
iniPath
|
||
kinematicsModuleId
|
||
switchkinsType
|
||
coordinates
|
||
```
|
||
|
||
RUN gate 增加检查:
|
||
|
||
```text
|
||
kinematicsRuntimeReadiness.loaded === true
|
||
kinematicsRuntimeReadiness.moduleId === profile.kinematicsModuleId
|
||
rtcpFrame.sourceMode === source-derived-kinematics-wasm
|
||
```
|
||
|
||
测试:
|
||
|
||
```bash
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_kinematics_runtime.mjs
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
|
||
```
|
||
|
||
## 7. 阶段 3:实现 machine-file staging
|
||
|
||
目标:task/HAL runtime 看到的 INI、HAL、remap、tool table、G-code 必须来自同一个 LinuxCNC machine context。
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/runtime/linuxcnc-machine-file-staging.js
|
||
app/src/state/store.js
|
||
```
|
||
|
||
实现内容:
|
||
|
||
```text
|
||
1. stageProfileMachineFiles(profile) 使用 profile.iniPath 和当前 iniText 生成 plan。
|
||
2. plan 必须包含:
|
||
profileId
|
||
iniPath
|
||
wasmDir
|
||
wasmIniPath
|
||
wasmProgramPath
|
||
files[]
|
||
taskHalSession
|
||
|
||
3. save 必须包含:
|
||
files[]
|
||
gcodeSources[]
|
||
gcodeFiles[]
|
||
summary
|
||
storageMode
|
||
storageCapability
|
||
|
||
4. files[] 至少包含:
|
||
当前 profile INI
|
||
当前 profile tool table
|
||
remap_subs/428remap.ngc
|
||
remap_subs/429remap.ngc
|
||
remap_subs/430remap.ngc
|
||
当前 profile demos 下的 G-code
|
||
|
||
5. selectMachineFileProgram(plan, save, sourceRel) 只能选择 LinuxCNC vendored five-axis demo G-code。
|
||
6. 选择 G-code 后,machineFileStaging.selectedGcodeSourceRel 和 plan.wasmProgramPath 必须同步更新。
|
||
```
|
||
|
||
状态更新:
|
||
|
||
```text
|
||
MACHINE_FILE_STAGING_STARTED
|
||
MACHINE_FILE_STAGING_COMPLETE
|
||
MACHINE_FILE_STAGING_FAILED
|
||
LOAD_LINUXCNC_GCODE_SOURCE
|
||
```
|
||
|
||
失败阻断:
|
||
|
||
```text
|
||
run blocked: LinuxCNC machine files not staged
|
||
run blocked: no machine-file G-code opened for task/HAL session
|
||
```
|
||
|
||
测试:
|
||
|
||
```bash
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_machine_file_staging.mjs
|
||
```
|
||
|
||
断言:
|
||
|
||
```text
|
||
1. xyzac-trt stage 后 save.summary.kinds.remap >= 3。
|
||
2. gcodeSources 包含 xyzac_switchkins_test_1.ngc。
|
||
3. selectedGcodeSourceRel 为空时 RUN 被阻断。
|
||
4. LOAD_LINUXCNC_GCODE_SOURCE 后 wasmProgramPath 指向 selected source。
|
||
5. xyzac/xyzbc 的 INI、tool table、parameter file 不混用。
|
||
```
|
||
|
||
## 8. 阶段 4:初始化 task/HAL session
|
||
|
||
目标:在 RUN 前完成等价于 LinuxCNC `EMC_TASK_PLAN_OPEN` 的程序打开动作。
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/runtime/linuxcnc-task-hal-runtime.js
|
||
app/src/runtime/linuxcnc-task-hal-worker-client.js
|
||
app/src/runtime/linuxcnc-task-hal-worker.js
|
||
app/src/state/store.js
|
||
```
|
||
|
||
实现内容:
|
||
|
||
```text
|
||
1. attach taskHalRuntime 后写入 taskHalRuntimeReadiness。
|
||
2. initializeTaskHalSession({ openProgram: true }) 执行:
|
||
buildTaskHalSessionFromMachineFiles(...)
|
||
taskHalRuntime.resetSession()
|
||
taskHalRuntime.initSession({ profileId, iniPath, iniText, programPath })
|
||
taskHalRuntime.stageFiles(session.files)
|
||
taskHalRuntime.openProgram(session.programPath)
|
||
dispatch TASK_HAL_SESSION_READY
|
||
readStatus()
|
||
dispatch TASK_HAL_STATUS_APPLIED
|
||
|
||
3. taskHalSession 必须记录:
|
||
profileId
|
||
iniPath
|
||
programPath
|
||
programSourceRel
|
||
files
|
||
semanticBoundary
|
||
|
||
4. readStatus() 必须能返回:
|
||
task.file 或 openedProgram.path
|
||
task.mode
|
||
task.state
|
||
task.interpState
|
||
task.nextProgramLine
|
||
task.openedLineCount
|
||
motionStatus.motion
|
||
ui.axisPose
|
||
ui.activeLine
|
||
ui.currentVelocity
|
||
ui.switchkinsType
|
||
```
|
||
|
||
RUN gate 增加检查:
|
||
|
||
```text
|
||
taskHalRuntime.loaded === true
|
||
taskHalRuntimeReadiness.taskRuntimeReady === true
|
||
taskHalRuntimeReadiness.motionRuntimeReady === true
|
||
taskHalRuntimeReadiness.halRuntimeReady === true
|
||
taskHalSession.programPath === expectedTaskHalProgramPathForState(state)
|
||
task status opened file === taskHalSession.programPath
|
||
```
|
||
|
||
测试:
|
||
|
||
```bash
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_task_hal_runtime.mjs
|
||
```
|
||
|
||
## 9. 阶段 5:实现 RUN preconditions gate
|
||
|
||
目标:所有 RUN 入口都先经过统一 gate。
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/state/store.js
|
||
app/src/state/linuxcnc-task-policy.js
|
||
```
|
||
|
||
函数:
|
||
|
||
```text
|
||
validateRunPreconditions(state, options)
|
||
expectedTaskHalProgramPathForState(state)
|
||
gateLinuxCncTaskAction(state, action)
|
||
```
|
||
|
||
实现顺序:
|
||
|
||
```text
|
||
1. 检查 supported profile: xyzac-trt / xyzbc-trt。
|
||
2. 检查 INI loaded + ready。
|
||
3. 检查 INI path 与 profile.iniPath 一致。
|
||
4. 检查 [TRAJ] COORDINATES 与 profile.traj.coordinates 一致。
|
||
5. 检查 [KINS] moduleId 与 profile.kinematicsModuleId 一致。
|
||
6. 检查 kinematics runtime loaded/moduleId/frame source。
|
||
7. 检查 machine files staged。
|
||
8. 检查 selectedGcodeSourceRel 存在。
|
||
9. 检查 task/HAL runtime readiness。
|
||
10. requireTaskHalSession=true 时检查 taskHalSession.programPath 与 expected program path 一致。
|
||
11. 检查 task status:
|
||
task state ON 或 RUN 前可 set ON
|
||
task mode AUTO 或 RUN 前可 set AUTO
|
||
homed/no_force_homing 满足
|
||
opened file 与 session programPath 一致
|
||
```
|
||
|
||
返回结构:
|
||
|
||
```js
|
||
{
|
||
apiName: "web-rtcp-5axis-run-preconditions",
|
||
ok: true,
|
||
profileId,
|
||
iniPath,
|
||
coordinates,
|
||
kinematicsModuleId,
|
||
selectedGcodeSourceRel,
|
||
programPath,
|
||
sourceMode: "linuxcnc-task-motion-hal-wasm",
|
||
semanticBoundary: "linuxcnc_ini_profile_kinematics_task_hal_run_preconditions"
|
||
}
|
||
```
|
||
|
||
失败返回:
|
||
|
||
```js
|
||
{
|
||
ok: false,
|
||
operatorMessage: "run blocked: ...",
|
||
profileId,
|
||
iniPath,
|
||
coordinates,
|
||
kinematicsModuleId
|
||
}
|
||
```
|
||
|
||
必须覆盖的阻断消息:
|
||
|
||
```text
|
||
run blocked: LinuxCNC INI not loaded
|
||
run blocked: LinuxCNC INI contract invalid
|
||
run blocked: machine profile and INI path mismatch
|
||
run blocked: machine profile and INI coordinates mismatch
|
||
run blocked: machine profile and INI kinematics mismatch
|
||
run blocked: LinuxCNC kinematics runtime not ready
|
||
run blocked: LinuxCNC kinematics module mismatch
|
||
run blocked: LinuxCNC kinematics frame not ready
|
||
run blocked: LinuxCNC machine files not staged
|
||
run blocked: no machine-file G-code opened for task/HAL session
|
||
run blocked: task/HAL runtime not ready
|
||
run blocked: task/HAL session program mismatch
|
||
run blocked: machine is not on
|
||
run blocked: machine is not homed
|
||
run blocked: task mode is not AUTO
|
||
```
|
||
|
||
测试:
|
||
|
||
```bash
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
|
||
```
|
||
|
||
## 10. 阶段 6:实现 RUN command sequence
|
||
|
||
目标:RUN 按 LinuxCNC task command 发送,不直接播放 fixture。
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/state/store.js
|
||
```
|
||
|
||
函数:
|
||
|
||
```text
|
||
runValidatedTaskHalProgramRun()
|
||
runTaskHalCommandSequence(commands, options)
|
||
```
|
||
|
||
实现步骤:
|
||
|
||
```text
|
||
1. preflight = validateRunPreconditions(state, { requireTaskHalSession: false })。
|
||
2. preflight 失败时只设置 operatorMessage,不改 activeLine、不启动 loop。
|
||
3. expectedProgramPath = expectedTaskHalProgramPathForState(state)。
|
||
4. 如果 taskHalSession 不存在,或 programPath 不等于 expectedProgramPath:
|
||
initializeTaskHalSession({ openProgram: true })
|
||
5. ready = validateRunPreconditions(state, { requireTaskHalSession: true })。
|
||
6. ready 失败时只设置 operatorMessage,不启动 loop。
|
||
7. stopTaskHalStatusLoop("restarted")。
|
||
8. 发送:
|
||
EMC_TASK_SET_STATE ON
|
||
EMC_TASK_SET_MODE AUTO
|
||
EMC_TASK_PLAN_RUN line=linuxcncStartLine
|
||
9. runCycles({ taskCycles: initialBatch })
|
||
10. readStatus()
|
||
11. dispatch TASK_HAL_STATUS_APPLIED
|
||
12. 如果 status 仍在 READING,启动 taskHalStatusLoop。
|
||
```
|
||
|
||
重要修正:
|
||
|
||
```text
|
||
普通 RUN 从头运行时 linuxcncStartLine=0。
|
||
不要用 activeLine - programStartLine 自动推导 run-from-line。
|
||
run-from-line 必须作为独立功能实现,并单独处理 previous modal state、安全提示和 LinuxCNC restart semantics。
|
||
```
|
||
|
||
`runTaskHalCommandSequence()` 要求:
|
||
|
||
```text
|
||
1. allowFixtureSession=false 时绝不创建 fixture task/HAL session。
|
||
2. 每条 command 都通过 taskHalRuntime.sendCommand(command)。
|
||
3. command 后必须 runCycles/readStatus 一次。
|
||
4. status 通过 TASK_HAL_STATUS_APPLIED 统一落入 store。
|
||
5. 失败派发 TASK_HAL_COMMAND_FAILED。
|
||
```
|
||
|
||
## 11. 阶段 7:实现 taskHalStatusLoop
|
||
|
||
目标:RUN 后持续推进 task/HAL runtime,并持续消费真实 status。
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/state/store.js
|
||
```
|
||
|
||
状态:
|
||
|
||
```js
|
||
taskHalStatusLoop: {
|
||
active,
|
||
sequence,
|
||
profileId,
|
||
iniPath,
|
||
kinematicsModuleId,
|
||
tickCount,
|
||
batchSize,
|
||
intervalMs,
|
||
taskPeriodNs,
|
||
servoPeriodNs,
|
||
lastStatusAt,
|
||
lastError,
|
||
stopReason
|
||
}
|
||
```
|
||
|
||
函数:
|
||
|
||
```text
|
||
startTaskHalStatusLoop(options)
|
||
runTaskHalStatusLoopTick(sequence)
|
||
stopTaskHalStatusLoop(reason, options)
|
||
shouldContinueTaskHalStatusLoop(state, status)
|
||
```
|
||
|
||
实现步骤:
|
||
|
||
```text
|
||
1. start 时先 stopTaskHalStatusLoop("restarted", notify=false)。
|
||
2. sequence 自增,写入 TASK_HAL_STATUS_LOOP_STARTED。
|
||
3. setTimeout 调度 runTaskHalStatusLoopTick(sequence)。
|
||
4. tick 内校验 loop.active、sequence、taskHalRuntime.loaded。
|
||
5. 调用 taskHalRuntime.runCycles({ taskPeriodNs, servoPeriodNs, taskCycles: batchSize })。
|
||
6. 调用 taskHalRuntime.readStatus()。
|
||
7. 如果 sequence 未过期,dispatch TASK_HAL_STATUS_APPLIED(status, loopSequence)。
|
||
8. shouldContinueTaskHalStatusLoop 为 true 时继续 setTimeout。
|
||
9. paused/complete/stopped/error 时 stopTaskHalStatusLoop。
|
||
```
|
||
|
||
停止条件:
|
||
|
||
```text
|
||
motion.aborted === true -> stopped
|
||
interpState === paused 或 motion.paused === true -> paused
|
||
interpState === idle 且 nextProgramLine >= openedLineCount -> complete
|
||
sequence 不匹配 -> ignore stale tick
|
||
runtime missing -> stopped
|
||
```
|
||
|
||
参数来源:
|
||
|
||
```text
|
||
taskPeriodNs 默认来自 [TASK] CYCLE_TIME。
|
||
servoPeriodNs 默认来自 [EMCMOT] SERVO_PERIOD。
|
||
batchSize 初始可为 5。
|
||
intervalMs 初始可为 25。
|
||
```
|
||
|
||
测试:
|
||
|
||
```bash
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs
|
||
```
|
||
|
||
断言:
|
||
|
||
```text
|
||
1. RUN 后 programRuntimeFeedbackHistory 至少 3 条。
|
||
2. 每条 sourceMode=linuxcnc-task-motion-hal-wasm。
|
||
3. semanticBoundary=linuxcnc_task_motion_hal_wasm_simulation_runtime。
|
||
4. taskCycle/servoCycle 单调递增。
|
||
5. STOP 后 taskHalStatusLoop.active=false。
|
||
```
|
||
|
||
## 12. 阶段 8:统一 TASK_HAL_STATUS_APPLIED
|
||
|
||
目标:所有 task/HAL 状态进入 UI 前只走一个 reducer patch。
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/state/store.js
|
||
```
|
||
|
||
函数:
|
||
|
||
```text
|
||
applyTaskHalStatusPatch(state, status, operatorMessage, options)
|
||
createTaskHalRuntimeFeedback(state, status, axisPose, activeLine)
|
||
resolveTaskHalAxisPose(state, status)
|
||
resolveTaskHalKinsType(state, status, activeLine)
|
||
rtcpStateFromKinsType(kinsType)
|
||
```
|
||
|
||
必须更新的 state:
|
||
|
||
```text
|
||
taskHalStatus
|
||
taskHalExecutionPending
|
||
activeLine
|
||
axisPose
|
||
kinsType
|
||
rtcpState
|
||
programExecutionSourceMode
|
||
machine.powerOn
|
||
machine.taskState
|
||
machine.mode
|
||
machine.interpState
|
||
machine.taskPaused
|
||
runState
|
||
feed.currentVelocity
|
||
programRuntimeFeedback
|
||
programRuntimeFeedbackHistory
|
||
taskHalStatusLoop.tickCount
|
||
taskHalStatusLoop.lastStatusAt
|
||
```
|
||
|
||
规则:
|
||
|
||
```text
|
||
1. activeLine 来自 status.ui.activeLine,不从 timer 或 sample index 推导。
|
||
2. axisPose 优先来自 status.ui.axisPose。
|
||
3. velocity 来自 status.ui.currentVelocity 或 motion current velocity。
|
||
4. switchkinsType 来自 status.ui.switchkinsType/HAL/motion,不从 G-code 文本猜测。
|
||
5. rtcpState 由 switchkinsType 映射。
|
||
6. runtime feedback history 最多保留 100 条。
|
||
7. axisPose、velocity、activeLine、DTG、kinsType 必须来自同一个 status。
|
||
```
|
||
|
||
需要重点防止:
|
||
|
||
```text
|
||
task-local 0 值覆盖 HOME/work pose。
|
||
canonical preview pose 覆盖 runtime feedback pose。
|
||
fixture playback sourceMode 混入 task/HAL history。
|
||
```
|
||
|
||
测试:
|
||
|
||
```bash
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_run_feedback_loop.mjs
|
||
```
|
||
|
||
## 13. 阶段 9:实现 STOP / ABORT / PAUSE / RESUME / STEP
|
||
|
||
目标:控制按钮与 LinuxCNC task command 对齐。
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/state/store.js
|
||
app/src/ui/gmoccapy-shell.js
|
||
```
|
||
|
||
行为:
|
||
|
||
```text
|
||
STOP/ABORT:
|
||
stopTaskHalStatusLoop("stopped")
|
||
send EMC_TASK_ABORT
|
||
runCycles/readStatus 一次
|
||
TASK_HAL_STATUS_APPLIED
|
||
|
||
PAUSE:
|
||
stopTaskHalStatusLoop("paused")
|
||
send EMC_TASK_PLAN_PAUSE
|
||
runCycles/readStatus 一次
|
||
runState=paused
|
||
|
||
RESUME:
|
||
send EMC_TASK_PLAN_RESUME
|
||
runCycles/readStatus 一次
|
||
如果 interpState 回到 reading,restart taskHalStatusLoop
|
||
|
||
STEP:
|
||
task/HAL runtime loaded 时不走 fixture sample playback
|
||
runCycles({ taskCycles: 1 })
|
||
readStatus 一次
|
||
TASK_HAL_STATUS_APPLIED
|
||
```
|
||
|
||
测试:
|
||
|
||
```text
|
||
1. RUN 后 PAUSE,loop active=false,runState=paused。
|
||
2. RESUME 后 loop 重新 active。
|
||
3. STOP 后 loop active=false,motion.aborted/stopped 状态进入 UI。
|
||
4. STEP 在 task/HAL loaded 时 sourceMode 仍是 linuxcnc-task-motion-hal-wasm。
|
||
```
|
||
|
||
## 14. 阶段 10:UI 显示与交互
|
||
|
||
目标:UI 只展示 store 中的 LinuxCNC-owned status,不推导运行状态。
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/ui/gmoccapy-shell.js
|
||
app/src/styles/gmoccapy.css
|
||
```
|
||
|
||
需要显示:
|
||
|
||
```text
|
||
1. INI readiness:
|
||
loaded/ready/path
|
||
kinematics
|
||
coordinates
|
||
jointCount/axisCount
|
||
|
||
2. Machine files:
|
||
staged/error
|
||
file count
|
||
gcode source selected
|
||
storage mode
|
||
|
||
3. Task/HAL readiness:
|
||
taskRuntimeReady
|
||
motionRuntimeReady
|
||
halRuntimeReady
|
||
profileId
|
||
iniPath
|
||
|
||
4. Runtime feedback:
|
||
sourceMode
|
||
line
|
||
sample
|
||
taskCycle
|
||
servoCycle
|
||
velocity
|
||
DTG
|
||
switchkinsType
|
||
|
||
5. Run history:
|
||
最近 10 条 programRuntimeFeedbackHistory。
|
||
```
|
||
|
||
交互要求:
|
||
|
||
```text
|
||
1. 选择 G-code 后必须 initializeTaskHalSession({ openProgram: true }) 或标记 session stale。
|
||
2. RUN button 只 dispatch RUN_MACHINE_FILE_PROGRAM / RUN action,不直接操作 line。
|
||
3. RUN blocked 时显示 operatorMessage。
|
||
4. UI 高亮行读取 state.activeLine。
|
||
5. DRO 读取 state.axisPose 或 state.programRuntimeFeedback.axisPose。
|
||
6. RTCP canvas 状态读取 state.rtcpState。
|
||
```
|
||
|
||
## 15. 阶段 11:full execution boundary
|
||
|
||
目标:让系统明确声明当前是不是完整 LinuxCNC task/HAL run,而不是含糊显示 ready。
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/src/runtime/full-execution-boundary.js
|
||
app/src/runtime/native-task-hal-audit.js
|
||
app/src/state/store.js
|
||
```
|
||
|
||
实现内容:
|
||
|
||
```text
|
||
1. fullLinuxCncProgramExecutionReady 只在这些条件都满足时为 true:
|
||
INI contract ready
|
||
profile/INI coordinates match
|
||
kinematics runtime ready
|
||
machine files staged
|
||
task runtime ready
|
||
motion runtime ready
|
||
HAL runtime ready
|
||
selected G-code opened
|
||
|
||
2. hardwareDrive=false。
|
||
3. hostRealtimeKernel=false。
|
||
4. externalUserMProcessReady=false。
|
||
5. semanticBoundary 明确标注:
|
||
linuxcnc_task_motion_hal_wasm_simulation_runtime
|
||
```
|
||
|
||
测试:
|
||
|
||
```bash
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_full_execution_boundary.mjs
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_native_task_hal_audit.mjs
|
||
```
|
||
|
||
## 16. 阶段 12:Node smoke 集成
|
||
|
||
目标:所有关键链路进入 `npm run smoke:node`。
|
||
|
||
文件:
|
||
|
||
```text
|
||
app/package.json
|
||
```
|
||
|
||
加入或确认:
|
||
|
||
```json
|
||
{
|
||
"scripts": {
|
||
"smoke:node": "node ../tests/node/verify_linuxcnc_kinematics_runtime.mjs && node ../tests/node/verify_linuxcnc_interpreter_runtime.mjs && node ../tests/node/verify_linuxcnc_ini_runtime.mjs && node ../tests/node/verify_run_preconditions.mjs && node ../tests/node/verify_run_feedback_loop.mjs && node ../tests/node/verify_linuxcnc_task_hal_runtime.mjs"
|
||
}
|
||
}
|
||
```
|
||
|
||
实际脚本可以保留现有顺序,但必须覆盖:
|
||
|
||
```text
|
||
INI parser
|
||
kinematics runtime
|
||
machine-file staging
|
||
task/HAL runtime
|
||
RUN preconditions
|
||
RUN feedback loop
|
||
store status patch
|
||
full boundary
|
||
```
|
||
|
||
执行:
|
||
|
||
```bash
|
||
npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
|
||
```
|
||
|
||
## 17. 阶段 13:浏览器证据采集
|
||
|
||
目标:证明浏览器 UI 中 RUN 不是一次性 status,也不是 fixture playback。
|
||
|
||
文件:
|
||
|
||
```text
|
||
qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs
|
||
```
|
||
|
||
新增 case:
|
||
|
||
```text
|
||
07-run-preconditions-and-feedback
|
||
```
|
||
|
||
浏览器步骤:
|
||
|
||
```text
|
||
1. 打开 app。
|
||
2. 等待 state.iniConfigReadiness.loaded === true。
|
||
3. 选择 xyzac-trt。
|
||
4. 确认 INI/profile/kinematics module 都是 xyzac-trt。
|
||
5. 选择 xyzac_switchkins_test_1.ngc。
|
||
6. 点击 POWER。
|
||
7. 点击 HOME。
|
||
8. 点击 AUTO。
|
||
9. 点击 RUN。
|
||
10. 采集 t=0.2s / 0.5s / 1.0s / 2.0s / 5.0s state。
|
||
11. 截图。
|
||
```
|
||
|
||
断言:
|
||
|
||
```text
|
||
1. taskHalStatusLoop.tickCount 增长。
|
||
2. programRuntimeFeedbackHistory.length 增长。
|
||
3. 每条 feedback sourceMode 是 linuxcnc-task-motion-hal-wasm。
|
||
4. activeLine 等于 UI 高亮行。
|
||
5. DRO 等于 state.programRuntimeFeedback.axisPose。
|
||
6. RTCP canvas 状态与 state.rtcpState 一致。
|
||
7. runtime feedback text 显示 taskCycle/servoCycle/line/velocity。
|
||
8. 没有 fixture-line-playback sourceMode。
|
||
```
|
||
|
||
输出:
|
||
|
||
```text
|
||
qa/web-rtcp-5axis-site-test/output/run-preconditions-feedback.json
|
||
qa/web-rtcp-5axis-site-test/screenshots/run-preconditions-feedback/*.png
|
||
```
|
||
|
||
## 18. 最终验收命令
|
||
|
||
基础:
|
||
|
||
```bash
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_ini_runtime.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_run_preconditions.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
|
||
node web-rtcp-5axis-sim-plan/tests/node/verify_rtcp_store.mjs
|
||
npm --prefix web-rtcp-5axis-sim-plan/app run build
|
||
```
|
||
|
||
完整:
|
||
|
||
```bash
|
||
npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
|
||
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
|
||
node qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs
|
||
```
|
||
|
||
## 19. 完成标准
|
||
|
||
功能完成必须同时满足:
|
||
|
||
```text
|
||
1. RUN 前能明确追溯到一个 LinuxCNC INI。
|
||
2. INI definition contract 完整通过。
|
||
3. profile/INI/kinematics runtime/moduleId/coordinates 一致。
|
||
4. machine-file staging 使用同一 profile 的 INI、HAL、remap、tool table、G-code。
|
||
5. selected G-code 已通过 taskHalRuntime.openProgram 打开。
|
||
6. machine ON、AUTO、homed/no_force_homing gate 明确。
|
||
7. RUN 发送 EMC_TASK_SET_STATE / EMC_TASK_SET_MODE / EMC_TASK_PLAN_RUN。
|
||
8. RUN 后 taskHalStatusLoop 持续推进 runCycles/readStatus。
|
||
9. activeLine、DRO、velocity、DTG、switchkinsType、RTCP 来自同一个 status snapshot。
|
||
10. PAUSE/RESUME/STOP/STEP 行为与 LinuxCNC task command 对齐。
|
||
11. Node smoke 通过。
|
||
12. 浏览器 JSON/截图证据证明 UI 正在消费 task/HAL/motion feedback。
|
||
13. fallback playback 不会冒充 LinuxCNC task/HAL runtime。
|
||
```
|
||
|
||
## 20. 建议实施顺序
|
||
|
||
推荐实际开发顺序:
|
||
|
||
```text
|
||
1. 补强 INI parser/validation。
|
||
2. 补 run_preconditions 测试。
|
||
3. 补 machine-file staging 对 xyzac/xyzbc 混用的断言。
|
||
4. 补 taskHalSession opened program status 字段。
|
||
5. 修正 RUN line=0 与 run-from-line 分离。
|
||
6. 扩展 validateRunPreconditions 的 ON/AUTO/homed/opened-file gate。
|
||
7. 确认 taskHalStatusLoop 使用 INI CYCLE_TIME/SERVO_PERIOD。
|
||
8. 扩展 TASK_HAL_STATUS_APPLIED 的 same-snapshot 断言。
|
||
9. 补 PAUSE/RESUME/STOP/STEP 测试。
|
||
10. 更新 UI runtime feedback/history。
|
||
11. 跑 smoke:node。
|
||
12. 补浏览器证据采集。
|
||
```
|
||
|
||
每完成一个阶段都先跑该阶段对应 node 测试,再进入下一阶段。
|