# AXIS 主控制按钮调用链与 WASM 完善指南 生成时间:2026-07-07 分析范围: - LinuxCNC 源码:`/home/mes123456/cnc_wams/linuxcnc` - WASM 迁移工程:`/home/mes123456/cnc_wams/wasm-port` - Web 5 轴仿真工程:`/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan` 本文重点分析 AXIS 主界面的“急停、上电、Home、执行、暂停、单步执行”的调用链、状态机制、按钮前置条件,以及对 WASM/Web 项目的完善建议。 ## 1. 总体架构结论 LinuxCNC 中 AXIS 按钮不是直接操作运动控制,而是走统一命令/状态通道: ```text AXIS Python/Tk 按钮 -> linuxcnc.command() Python C 扩展 -> NML 命令对象 EMC_TASK_*/EMC_JOINT_* -> emcCommand NML 通道 -> task 主循环 emcTaskPlan()/emcTaskIssueCommand() -> taskintf.cc 转为 EMCMOT_* motion 命令 -> motion 实时侧 command.c/control.c/homing.c -> motion/status + task/status 合成为 emcStatus -> linuxcnc.stat().poll() 供 AXIS 刷新 UI ``` 核心状态不是由按钮自行保存,而是由 `emcStatus` 聚合: - `emcStatus->task.state`:任务状态,GUI 显示为 `STATE_ESTOP`、`STATE_ESTOP_RESET`、`STATE_ON` 等。 - `emcStatus->task.mode`:任务模式,`MODE_MANUAL`、`MODE_AUTO`、`MODE_MDI`。 - `emcStatus->task.interpState`:解释器状态,`INTERP_IDLE`、`INTERP_READING`、`INTERP_PAUSED`、`INTERP_WAITING`。 - `emcStatus->task.task_paused`:task 层暂停标志。 - `emcStatus->motion.traj.paused`:motion 轨迹规划器暂停标志。 - `emcStatus->motion.traj.single_stepping`:task/GUI 可见的单步执行标志。 - `emcStatus->motion.joint[n].homed`:每个 joint 的回零状态。 - `emcStatus->motion.traj.enabled`:motion 是否上使能,用于推导 task 是否 `STATE_ON`。 需要特别注意:LinuxCNC 的 task 层主要是 C++,motion 层主要是 C。用户所说的 C++ 实现,在源码中实际跨越 `emctask*.cc`、`taskintf.cc` 和 motion 的 `command.c`、`homing.c`。 ## 2. AXIS UI 按钮入口 AXIS 主界面位于: - `linuxcnc/bin/axis` 关键辅助函数: ```python def manual_ok(do_poll=True): if do_poll: s.poll() if s.task_state != linuxcnc.STATE_ON: return False return s.interp_state == linuxcnc.INTERP_IDLE or ( s.task_mode == linuxcnc.MODE_MDI and s.queued_mdi_commands < vars.max_queued_mdi_commands.get() ) def ensure_mode(m, *p): s.poll() if s.task_mode == m or s.task_mode in p: return True c.mode(m) c.wait_complete() s.poll() return True ``` 源码位置:`linuxcnc/bin/axis:986-1015`。 这说明 AXIS 侧前置条件分两层: 1. UI 层用 `s.poll()` 读当前状态,避免明显非法操作。 2. task/motion 层仍会再次校验,非法命令会拒绝或报错。 ## 3. Python C 扩展到 NML 命令 AXIS 使用 `linuxcnc.command()`。该对象由 `linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc` 暴露,关键映射如下: | AXIS 调用 | C 扩展函数 | NML 命令 | | --- | --- | --- | | `c.state(STATE_*)` | `state()` | `EMC_TASK_SET_STATE` | | `c.mode(MODE_*)` | `mode()` | `EMC_TASK_SET_MODE` | | `c.home(joint)` | `home()` | `EMC_JOINT_HOME` | | `c.auto(AUTO_RUN, line)` | `emcauto()` | `EMC_TASK_PLAN_RUN` | | `c.auto(AUTO_PAUSE)` | `emcauto()` | `EMC_TASK_PLAN_PAUSE` | | `c.auto(AUTO_RESUME)` | `emcauto()` | `EMC_TASK_PLAN_RESUME` | | `c.auto(AUTO_STEP)` | `emcauto()` | `EMC_TASK_PLAN_STEP` | 源码依据: - `mode()` 创建 `EMC_TASK_SET_MODE`:`linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:1691-1705` - `state()` 创建 `EMC_TASK_SET_STATE`:`linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:1815-1829` - `home()` 创建 `EMC_JOINT_HOME`:`linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:1941-1945` - `emcauto()` 分发 `RUN/PAUSE/RESUME/STEP`:`linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:2093-2116` ## 4. task 主循环的命令调度 task 主循环在 `linuxcnc/src/emc/task/emctaskmain.cc`: ```text emcCommandBuffer->read() -> emcTaskPlan() -> emcTaskExecute() -> emcMotionUpdate(&emcStatus->motion) -> emcTaskUpdate(&emcStatus->task) -> 写 emcStatus 顶层 command/status/echo_serial_number ``` 源码位置:`linuxcnc/src/emc/task/emctaskmain.cc:3301-3478`。 `emcTaskPlan()` 按三类状态进行门禁: 1. `task.state`:`OFF`、`ESTOP`、`ESTOP_RESET`、`ON` 2. `task.mode`:`MANUAL`、`AUTO`、`MDI` 3. `task.interpState`:`IDLE`、`READING`、`PAUSED`、`WAITING` 因此,AXIS UI 的按钮条件必须与 `emcTaskPlan()` 的状态表一致,不能只看 `runState` 这类 Web 自定义状态。 ## 5. 急停按钮 ### 5.1 AXIS UI 调用链 源码:`linuxcnc/bin/axis:2243-2248` ```python def estop_clicked(event=None): s.poll() if s.task_state == linuxcnc.STATE_ESTOP: c.state(linuxcnc.STATE_ESTOP_RESET) else: c.state(linuxcnc.STATE_ESTOP) ``` 按钮语义: - 当前是 `STATE_ESTOP`:发送 `STATE_ESTOP_RESET`,表示解除急停。 - 其他任意状态:发送 `STATE_ESTOP`,进入急停。 ### 5.2 NML/task 调用链 ```text c.state(STATE_ESTOP or STATE_ESTOP_RESET) -> emcmodule.cc state() -> EMC_TASK_SET_STATE -> emcTaskPlan() -> emcTaskIssueCommand() -> emcTaskSetState() ``` `EMC_TASK_SET_STATE` 被 task 作为 immediate command 接收: - 非 ON 状态也允许接收:`emctaskmain.cc:795-829` - ON/MANUAL 允许:`emctaskmain.cc:897-956` - ON/AUTO/IDLE 允许:`emctaskmain.cc:1026-1075` - ON/AUTO/READING 允许:`emctaskmain.cc:1137-1174` - ON/AUTO/PAUSED 允许:`emctaskmain.cc:1206-1251` ### 5.3 C++ task 状态处理 `emcTaskSetState(ESTOP)` 源码:`linuxcnc/src/emc/task/emctask.cc:353-365` 执行内容: - `emcMotionAbort()`:中止 motion。 - `emcSpindleAbort()`:中止主轴。 - `emcAuxEstopOn()`:IO 急停输出置位。 - `emcTrajDisable()`:下发 motion disable。 - 关闭 flood coolant。 - `emcTaskAbort()`:清解释器队列、清 pause/step 状态。 - `emcIoAbort(TASK_STATE_ESTOP)`。 - `emcJointUnhome(-2)`:清除 `VOLATILE_HOME` joint 的 homed。 - `emcTaskPlanSynch()`:同步解释器。 `emcTaskSetState(ESTOP_RESET)` 源码:`linuxcnc/src/emc/task/emctask.cc:342-350` 执行内容: - `emcAuxEstopOff()`:解除 IO 急停。 - `emcTaskAbort()`:清程序运行状态。 - `emcIoAbort(TASK_STATE_ESTOP_RESET)`。 - `emcSpindleAbort()`。 - `emcTaskPlanSynch()`。 ### 5.4 状态记录方式 task 并不简单把 `task.state = ESTOP` 固定写死,而是周期性通过 `determineState()` 从 IO 和 motion 推导: 源码:`linuxcnc/src/emc/task/emctask.cc:418-428` 规则: | IO 急停 | motion enabled | 推导状态 | | --- | --- | --- | | true | 任意 | `ESTOP` | | false | false | `ESTOP_RESET` | | false | true | `ON` | `emcTaskUpdate()` 每周期写入 `stat->state`:`linuxcnc/src/emc/task/emctask.cc:688-699`。 这意味着 Web/WASM 中不能只维护一个 `estopActive` 布尔值;应该同时维护: - IO estop 状态; - motion enable 状态; - task 推导状态; - volatile home 清除动作。 ## 6. 上电/下电按钮 ### 6.1 AXIS UI 调用链 源码:`linuxcnc/bin/axis:2250-2260` ```python def onoff_clicked(event=None): s.poll() if s.task_state == linuxcnc.STATE_ESTOP_RESET: c.state(linuxcnc.STATE_ON) ... else: c.state(linuxcnc.STATE_OFF) ``` 按钮语义: - 只有 `STATE_ESTOP_RESET` 才能上电。 - 其他状态一律发送 `STATE_OFF`。 - 因此 `STATE_ESTOP` 下点电源不是“上电”,而是进入 OFF/保持不可上电语义;必须先通过急停按钮解除急停到 `ESTOP_RESET`。 ### 6.2 task 调用链 ```text c.state(STATE_ON/OFF) -> EMC_TASK_SET_STATE -> emcTaskSetState(ON/OFF) ``` `emcTaskSetState(ON)` 源码:`linuxcnc/src/emc/task/emctask.cc:336-340` 执行内容: - `emcTrajEnable()`。 - 关闭 flood coolant。 `emcTrajEnable()` 源码:`linuxcnc/src/emc/task/taskintf.cc:1396-1400` ```text emcTrajEnable() -> emcmotCommand.command = EMCMOT_ENABLE -> usrmotWriteEmcmotCommand() ``` motion 侧 `EMCMOT_ENABLE` 源码:`linuxcnc/src/emc/motion/command.c:1370-1384` 执行内容: - 如果 HAL `motion.enable` 输入为 false,报错:`can't enable motion, enable input is false`。 - 否则设置 `emcmotInternal->enabling = 1`,实际使能在 motion 控制周期中完成。 `emcTaskSetState(OFF)` 源码:`linuxcnc/src/emc/task/emctask.cc:323-333` 执行内容: - `emcMotionAbort()`。 - `emcSpindleAbort()`。 - `emcTrajDisable()`。 - `emcIoAbort(TASK_STATE_OFF)`。 - `emcTaskAbort()`。 - `emcJointUnhome(-2)` 清 volatile home。 - `emcTaskPlanSynch()`。 motion 侧 `EMCMOT_DISABLE` 源码:`linuxcnc/src/emc/motion/command.c:1357-1368` 执行内容: - 设置 `emcmotInternal->enabling = 0`。 - 对 inverse-only kinematics,还会退出 teleop/coord。 ### 6.3 状态记录方式 上电后并不是由 task 直接写 `STATE_ON`,而是 motion 报告 enabled 后,`determineState()` 推导为 `ON`。 motion 状态更新路径: ```text motion command/control 周期更新 emcmotStatus.motionFlag -> taskintf.cc emcMotionUpdate() -> emcTrajUpdate() -> stat->traj.enabled -> emcTaskUpdate() -> determineState() ``` 关键源码: - `emcMotionUpdate()` 读取 `emcmotStatus`:`linuxcnc/src/emc/task/taskintf.cc:2057-2102` - `emcTrajUpdate()` 写 `stat->enabled`:`linuxcnc/src/emc/task/taskintf.cc:1639-1652` - `determineState()` 推导状态:`linuxcnc/src/emc/task/emctask.cc:418-428` ## 7. Home 按钮 ### 7.1 AXIS UI 调用链 Home All 源码:`linuxcnc/bin/axis:2732-2740` ```python def home_all_joints(event=None): if not manual_ok(): return ensure_mode(linuxcnc.MODE_MANUAL) ... go_home(-1) ``` 单 joint Home 源码:`linuxcnc/bin/axis:2747-2767` 关键条件: - `manual_ok()` 必须为真,即 machine 为 `STATE_ON`,解释器 idle 或 MDI 队列可接受。 - 切到 `MODE_MANUAL`。 - 已 homed 时弹窗确认是否 re-home。 - 非 identity kinematics 下,单轴/单 joint 选择有额外限制。 - 最终 `go_home(num)` 调 `c.home(num)`。 ### 7.2 NML/task/motion 调用链 ```text c.home(-1 或 joint) -> emcmodule.cc home() -> EMC_JOINT_HOME -> emcTaskPlan() -> emcTaskIssueCommand() -> emcJointHome() -> EMCMOT_JOINT_HOME -> motion command.c -> do_home_joint() -> homing.c 状态机 ``` `EMC_JOINT_HOME` 只在 ON/MANUAL 中直接允许:`linuxcnc/src/emc/task/emctaskmain.cc:897-956`。 `emcJointHome()` 源码:`linuxcnc/src/emc/task/taskintf.cc:796-805` 执行内容: - 检查 joint 范围。 - 下发 `EMCMOT_JOINT_HOME`。 motion 侧 `EMCMOT_JOINT_HOME` 源码:`linuxcnc/src/emc/motion/command.c:1411-1439` motion 前置条件: - `emcmotStatus->motion_state == EMCMOT_MOTION_FREE`,必须处于 joint/free 模式。 - `motion.homing-inhibit` 不能为 true。 - 当前不能已有 homing 流程在跑。 - `GET_MOTION_ENABLE_FLAG()` 必须为 true。 - 负 joint 表示 home all。 ### 7.3 homing 状态机 `do_home_joint()` 最终进入 `linuxcnc/src/emc/motion/homing.c`。 核心状态记录: - `H[joint].homing` - `H[joint].homed` - `H[joint].home_state` - `H[joint].joint_in_sequence` - `joint.free_tp.enable` 输出 HAL pin: 源码:`linuxcnc/src/emc/motion/homing.c:541-573` - `joint.N.homing` - `joint.N.homed` - `joint.N.home-state` - `joint.N.index-enable` 完成逻辑: 源码:`linuxcnc/src/emc/motion/homing.c:1376-1385` - `homing = 0` - `homed = 1` - `home_state = HOME_IDLE` - `joint_in_sequence = 0` 异常中止: 源码:`linuxcnc/src/emc/motion/homing.c:1387-1397` - 所有 joint `homing = 0` - 所有 joint `homed = 0` - 所有 `free_tp.enable = 0` - `home_state = HOME_IDLE` ### 7.4 状态记录方式 task 通过 `emcMotionUpdate()` 将 motion homed 状态复制到 `emcStatus->motion.joint[n].homed`,AXIS 的 `s.homed[n]` 来自这里。 `task` 的 `all_homed()` 直接查 `emcStatus->motion.joint[i].homed`,源码:`linuxcnc/src/emc/task/emctaskmain.cc:151-152`。 ## 8. 执行 Run 按钮 ### 8.1 AXIS UI 调用链 源码:`linuxcnc/bin/axis:2379-2393` ```python def task_run(*event): res = run_warn() ... ensure_mode(linuxcnc.MODE_AUTO) c.auto(linuxcnc.AUTO_RUN, program_start_line) program_start_line = 0 ``` AXIS UI 层条件: - 会执行 `run_warn()`,处理文件变更、保存、运行前警告等。 - `ensure_mode(MODE_AUTO)` 切入 AUTO。 - 然后发送 `AUTO_RUN`。 真正硬条件在 task 层: - machine 必须 ON。 - AUTO 模式下允许。 - 未 homed 且没有 `no_force_homing` 时拒绝。 - 程序文件应已打开;未打开时会尝试打开 `emcStatus->task.file`。 ### 8.2 task 调用链 ```text c.auto(AUTO_RUN, line) -> EMC_TASK_PLAN_RUN -> emcTaskPlan() 按状态门禁 -> emcTaskIssueCommand() -> case EMC_TASK_PLAN_RUN ``` `EMC_TASK_PLAN_RUN` 在 ON/AUTO/IDLE 中允许:`linuxcnc/src/emc/task/emctaskmain.cc:1015-1075`。 核心处理源码:`linuxcnc/src/emc/task/emctaskmain.cc:2318-2335` 执行内容: - 如果未 homed 且 `!no_force_homing`,报错:`Can't run a program when not homed`。 - 清 `motion.traj.single_stepping`。 - 清 `stepping`、`steppingWait`。 - 必要时 `emcTaskPlanOpen(emcStatus->task.file)`。 - 记录 `programStartLine = run_msg->line`。 - 设置 `emcStatus->task.interpState = READING`。 - 设置 `emcStatus->task.task_paused = 0`。 ### 8.3 解释器和 motion 下发 Run 后 `emcTaskPlan()` 进入 `READING`,周期性调用 readahead 逻辑读取 G-code。解释器通过 canonical 函数把运动命令排入 `interp_list`,`emcTaskExecute()` 再把 motion 命令下发到 motion。 运动命令如直线/圆弧最终在 motion `command.c` 中以 `EMCMOT_SET_LINE`、`EMCMOT_SET_CIRCLE` 等处理。运动状态通过 `emcmotStatus.id`、`depth`、`activeDepth`、`paused`、`current_vel` 等回传。 ### 8.4 状态记录方式 Run 后主要状态变化: | 字段 | 变化 | | --- | --- | | `task.mode` | `AUTO` | | `task.interpState` | `READING` | | `task.task_paused` | `0` | | `motion.traj.single_stepping` | `0` | | `motion.traj.queue/activeQueue` | 随 motion 队列变化 | | `motion.traj.id/tag` | 跟随当前运动段与源代码 state tag | `emcStatus->task.status` 顶层状态由 `emctaskmain.cc:3458-3476` 合成:有错误为 `ERROR`,全部完成且解释器 idle 为 `DONE`,否则为 `EXEC`。 ## 9. 暂停 Pause 按钮 ### 9.1 AXIS UI 调用链 源码:`linuxcnc/bin/axis:2402-2406` ```python def task_pause(*event): if s.task_mode != linuxcnc.MODE_AUTO or s.interp_state not in ( linuxcnc.INTERP_READING, linuxcnc.INTERP_WAITING ): return ensure_mode(linuxcnc.MODE_AUTO) c.auto(linuxcnc.AUTO_PAUSE) ``` AXIS Pause 的严格条件: - `task_mode == MODE_AUTO` - `interp_state in (INTERP_READING, INTERP_WAITING)` - 不是仅凭 `runState == running` 工具栏合并暂停/恢复 `task_pauseresume()` 源码:`linuxcnc/bin/axis:2433-2443` - 仅 `MODE_AUTO` 或 `MODE_MDI` 可操作。 - 如果 `s.paused` 为真,发送 resume。 - 否则如果解释器不是 idle,发送 pause。 ### 9.2 task/motion 调用链 ```text c.auto(AUTO_PAUSE) -> EMC_TASK_PLAN_PAUSE -> emcTaskPlan() -> emcTaskIssueCommand() -> emcTrajPause() -> EMCMOT_PAUSE -> tpPause() ``` task 处理源码:`linuxcnc/src/emc/task/emctaskmain.cc:2337-2345` 执行内容: - `emcTrajPause()` - 若当前不是 `PAUSED`,保存 `interpResumeState = interpState` - `task.interpState = PAUSED` - `task.task_paused = 1` `emcTrajPause()` 源码:`linuxcnc/src/emc/task/taskintf.cc:1417-1421` motion 侧 `EMCMOT_PAUSE` 源码:`linuxcnc/src/emc/motion/command.c:1234-1240` 执行内容: - `tpPause(&coord_tp)` - `emcmotStatus->paused = 1` ### 9.3 状态记录方式 Pause 后必须同时体现: - `task.interpState = PAUSED` - `task.task_paused = 1` - `motion.traj.paused = 1` - `interpResumeState` 保存暂停前的 `READING` 或 `WAITING` Web 项目必须避免只改 UI `runState = paused`,否则 resume/step 会缺少 LinuxCNC 必需的恢复上下文。 ## 10. 单步 Step 按钮 ### 10.1 AXIS UI 调用链 源码:`linuxcnc/bin/axis:2395-2400` ```python def task_step(*event): if s.task_mode != linuxcnc.MODE_AUTO or s.interp_state != linuxcnc.INTERP_IDLE: o.set_highlight_line(None) if run_warn(): return ensure_mode(linuxcnc.MODE_AUTO) c.auto(linuxcnc.AUTO_STEP) ``` AXIS Step 的含义不是“前端播放下一帧”,而是向 task 发送 `EMC_TASK_PLAN_STEP`。task 根据解释器状态决定: - AUTO/IDLE:先启动程序,再立刻暂停,进入单步准备态。 - AUTO/READING 或 WAITING:设置 single stepping,下一段读入/执行后停住。 - AUTO/PAUSED:若 motion 队列中有暂停运动,发 `EMCMOT_STEP` 让 motion 走到下一段;否则恢复解释器状态继续读。 ### 10.2 task 处理 AUTO/IDLE 中 `STEP` 源码:`linuxcnc/src/emc/task/emctaskmain.cc:1078-1091` 执行内容: - 构造 `taskPlanRunCmd.line = 0` - 调 `emcTaskIssueCommand(&taskPlanRunCmd)` 启动程序 - `emcTrajPause()` - 保存 `interpResumeState` - `task.interpState = PAUSED` - `task.task_paused = 1` AUTO/READING 中 `STEP` 源码:`linuxcnc/src/emc/task/emctaskmain.cc:1178-1182` 执行内容: - `motion.traj.single_stepping = 1` - `stepping = 1` - `steppingWait = 0` AUTO/PAUSED 中 `STEP` 源码:`linuxcnc/src/emc/task/emctaskmain.cc:1254-1266` 执行内容: - `motion.traj.single_stepping = 1` - `stepping = 1` - 如果 `motion.traj.paused && queue > 0`,调用 `emcTrajStep()` - 否则 `task.interpState = interpResumeState` - `task.task_paused = 1` AUTO/WAITING 中 `STEP` 源码:`linuxcnc/src/emc/task/emctaskmain.cc:1330-1334` 执行内容同 READING:设置单步标志。 ### 10.3 motion 处理 `emcTrajStep()` 源码:`linuxcnc/src/emc/task/taskintf.cc:1438-1442` motion 侧 `EMCMOT_STEP` 源码:`linuxcnc/src/emc/motion/command.c:1265-1273` 处理逻辑: - 如果 motion 当前 paused: - 记录 `idForStep = emcmotStatus->id` - `emcmotStatus->stepping = 1` - resume 轨迹规划器,使其执行到 id 变化或下一步边界再停。 ### 10.4 状态记录方式 单步相关字段: - task 侧静态变量:`stepping`、`steppingWait` - task 可见状态:`emcStatus->motion.traj.single_stepping` - motion 状态:`emcmotStatus->stepping`、`idForStep`、`paused` - 解释器状态:`PAUSED` 或恢复到 `interpResumeState` Web 中若只实现“数组 frameIndex++”,不能等价于 LinuxCNC Step。正确做法是把 Step 作为 task 命令,驱动 task/motion 的 pause/step 状态机。 ## 11. Resume 补充 虽然用户重点列出“急停、上电、Home、执行、暂停、单步执行”,但暂停机制必须包含 Resume 才完整。 AXIS `task_resume()` 源码:`linuxcnc/bin/axis:2424-2431` 条件: - `s.paused` 为真。 - `task_mode` 是 `AUTO` 或 `MDI`。 - 发送 `AUTO_RESUME`。 task 处理源码:`linuxcnc/src/emc/task/emctaskmain.cc:2369-2376` 执行内容: - `emcTrajResume()` - `task.interpState = interpResumeState` - `task.task_paused = 0` - `motion.traj.single_stepping = 0` - 清 `stepping`、`steppingWait` motion 侧 `EMCMOT_RESUME` 源码:`linuxcnc/src/emc/motion/command.c:1256-1263` 执行内容: - `emcmotStatus->stepping = 0` - `tpResume()` - `emcmotStatus->paused = 0` ## 12. 按钮前置条件汇总 | 按钮 | AXIS UI 前置条件 | task/motion 硬条件 | 执行后状态 | | --- | --- | --- | --- | | 急停 | 无;读 `s.task_state` 决定 reset 或 estop | `EMC_TASK_SET_STATE` 在多数状态允许 | `ESTOP` 分支 abort、disable、IO estop on;reset 分支 IO estop off、abort、同步解释器 | | 上电 | 只有 `STATE_ESTOP_RESET` 发送 ON,否则发送 OFF | ON 要 motion enable 输入为真;OFF 总是 abort/disable | ON 后由 `motion.traj.enabled` 推导 `STATE_ON`;OFF 后推导 `ESTOP_RESET` 或 OFF 语义并清 volatile home | | Home | `manual_ok()`;切 `MODE_MANUAL`;必要时 re-home 确认 | motion 必须 FREE、enabled、未 homing、未 inhibit | `joint.N.homing` 过程态;完成 `joint.N.homed=1`;失败清 homed | | Run | `run_warn()`;切 `MODE_AUTO` | ON/AUTO/IDLE;已 homed 或 no_force_homing;程序打开 | `interpState=READING`,`task_paused=0`,motion 队列开始增长 | | Pause | `MODE_AUTO` 且 `INTERP_READING/WAITING` | `EMC_TASK_PLAN_PAUSE`;motion `EMCMOT_PAUSE` | `interpState=PAUSED`,`task_paused=1`,`motion.paused=1`,保存 resume state | | Step | 切 `MODE_AUTO`;非 idle 时可先 run_warn | 按 AUTO/IDLE/READING/PAUSED/WAITING 分支处理 | 设置 `single_stepping`;必要时 `EMCMOT_STEP`;保持/进入 paused | | Resume | `paused` 且 `MODE_AUTO/MDI` | `EMC_TASK_PLAN_RESUME`;motion `EMCMOT_RESUME` | 恢复 `interpResumeState`,清 `task_paused/single_stepping/stepping` | ## 13. WASM-port 完善建议 当前 `wasm-port` 已有 task/HAL WASM 入口: - `wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_task_hal_wasm.cpp` - `wasm-port/runtime/sdk/src/linuxcnc-task-hal.js` 但是 `linuxcnc_task_hal_wasm.cpp` 仍是 `linuxcnc_task_motion_hal_wasm_phase4_minimal`,存在明显过渡实现: - 用字符串搜索和手写 JSON 解析命令。 - 自己维护 `TaskRuntime.state/mode/interp_state/task_paused/single_stepping`。 - 从 G-code 文本中用简单规则派生 motion,存在 G-code 语义重实现风险。 - `readiness()` 中仍标记 `nativeTaskReady:false`、`nativeHalSyncReady:false`。 这与 `wasm-port/AGENTS.md` 的要求冲突风险较高:项目应复用 LinuxCNC source,不应扩展项目自写 CNC 语义。 建议按以下阶段完善。 ### 13.1 第一阶段:把状态枚举和门禁表固化为 LinuxCNC 镜像 目标:先不改大架构,但移除随意字符串状态。 建议: 1. 在 `runtime/core/linuxcnc_wrap` 增加 task 状态模型头文件,例如 `linuxcnc_task_state_model.hh`。 2. 用与 LinuxCNC 一致的枚举名:`ESTOP`、`ESTOP_RESET`、`ON`、`MANUAL`、`AUTO`、`MDI`、`IDLE`、`READING`、`PAUSED`、`WAITING`。 3. 实现一个 `task_accepts_command(state, mode, interp, command)`,逐项映射 `emcTaskPlan()` 的允许表。 4. 所有 `lctask_send_command_json()` 先走该门禁表,返回明确错误码和错误文本。 5. 增加测试覆盖: - ESTOP 下仅允许 state/mode/config 等 immediate 命令。 - ON/MANUAL 下允许 HOME,不允许 AUTO RUN。 - ON/AUTO/IDLE 下允许 RUN/STEP。 - ON/AUTO/READING 下允许 PAUSE/STEP,不允许 HOME。 - ON/AUTO/PAUSED 下允许 RESUME/STEP。 ### 13.2 第二阶段:替换手写 G-code motion 派生 当前 `enqueue_linear_move_from_line()` 和 Web 侧 `buildSourceLineMotionSegments()` 都会从 G-code 行解析轴和 feed,这属于语义漂移高风险点。 正确方向: 1. G-code 到 canonical motion 必须由 vendored LinuxCNC interpreter 产生。 2. task/HAL runtime 只消费 LinuxCNC canonical 输出或已验证的 motion plan,不自行解释 G-code。 3. 对没有 motion plan 的程序,应返回“需要 interpreter plan”错误,而不是 fallback 解析 `G0/G1`。 4. `buildTaskHalProgramMotionPlan()` 可保留为 host 边界适配器,但输入必须来自 LinuxCNC interpreter runtime 的 canonical events,而不是源码行正则解析。 ### 13.3 第三阶段:引入 vendored task/motion 源码闭环 最终目标不是仿照 `emctaskmain.cc` 写一份 JS/C++ 状态机,而是逐步把 vendored LinuxCNC task/motion 代码纳入 WASM: 1. 将 `emc/nml_intf/emc_nml.hh` 中的命令/状态结构作为 WASM 内部 ABI 参考。 2. 用内存队列替代 NML transport,但保留 `EMC_TASK_*` / `EMCMOT_*` 命令类型。 3. 将 `taskintf.cc` 的 `emcTrajEnable/Pause/Resume/Step/Home` 路由接到 `linuxcnc_motion_runtime.c`。 4. 将 `emcTaskSetState()`、`emcTaskSetMode()`、`emcTaskAbort()` 的语义直接迁移或编译进 WASM。 5. motion runtime 应逐步引入 `command.c`、`homing.c`、`control.c` 中真实状态更新,而不是只暴露“看起来相同”的 JSON status。 ### 13.4 第四阶段:状态发布完全对齐 `emcStatus` WASM SDK `readStatus()` 应输出与 Python `linuxcnc.stat()` 结构同构的字段: - `task.state` - `task.mode` - `task.interpState` - `task.execState` - `task.taskPaused` - `motion.traj.enabled` - `motion.traj.paused` - `motion.traj.singleStepping` - `motion.traj.queue` - `motion.traj.id` - `motion.joint[n].homed` - `motion.joint[n].homing` - `motion.joint[n].homeState` - `echoSerialNumber` - `commandType` 这样 Web 项目可以直接消费 LinuxCNC 状态,而不是维护第二套状态。 ## 14. web-rtcp-5axis-xyzbc-trt-sim-plan 完善建议 当前 Web 项目已有: - `app/src/state/linuxcnc-task-policy.js` - `app/src/state/store.js` - `app/src/runtime/linuxcnc-task-hal-runtime.js` - browser/node 对按钮路径的验证 这已经完成一部分 AXIS 对标,但还需要继续收敛。 ### 14.1 状态源优先级必须调整 建议状态优先级: ```text taskHalStatus.ui/task/motion 原生状态 > LinuxCNC interpreter canonical events > Web UI 派生状态 ``` Web store 中 `machine.taskState/mode/interpState/runState` 应逐渐变成 task/HAL 状态的投影,而不是独立真相源。 ### 14.2 按钮 action 应发送 LinuxCNC 命令,不直接改业务状态 按钮处理建议统一为: ```text UI click -> gateLinuxCncTaskAction() -> taskHalRuntime.sendCommand({ type: "EMC_TASK_*" }) -> taskHalRuntime.runCycles() -> taskHalRuntime.readStatus() -> store 接收 STATUS_SYNC -> UI 渲染 ``` 避免: - click 后直接 `runState = "running"`; - click 后直接 `machine.allHomed = true`; - pause/step 直接操作播放帧; - power 直接改 `powerOn` 而不等 motion enabled/status。 ### 14.3 补齐 Home 状态过程态 Web 当前常见实现是 `HOME` 后立即 `allHomed=true`。LinuxCNC 真实机制是: ```text EMC_JOINT_HOME -> joint.N.homing = true -> home_state 多阶段变化 -> joint.N.homed = true ``` 建议 Web 状态增加: - `machine.joints[n].homing` - `machine.joints[n].homed` - `machine.joints[n].homeState` - `machine.homingActive` - `machine.allHomed` 按钮门禁: - homing 过程中禁止再次 Home。 - Run 前使用 `allHomed || noForceHoming`。 - OFF/ESTOP 时清 volatile home,而不是无条件清所有 home;如果当前模型无法区分 volatile,先在文档和状态字段中标注“volatile home 简化”。 ### 14.4 补齐 Step 的 LinuxCNC 语义 Web 需要区分三种 Step: 1. 程序 idle 首次 step:等价于 RUN 后立即 PAUSE。 2. 程序 running 中 step:设置 single stepping,等待下一段边界停住。 3. 程序 paused 中 step:若 motion queue 有内容,发送 motion step;否则恢复 interpreter 读下一段。 建议在 `linuxcnc-task-policy.js` 中把 `STEP` 门禁拆开: - `canStepFromIdle` - `canStepWhileReading` - `canStepWhilePaused` - `canStepWhileWaiting` 并在 status 中显示: - `task.singleStepping` - `motion.stepping` - `motion.idForStep` - `motion.paused` ### 14.5 Pause/Resume 继续保持 AXIS 门禁 已完成的严格对标应继续保留: - `PAUSE`:仅 `STATE_ON + MODE_AUTO + INTERP_READING/WAITING`。 - `PAUSE_RESUME`:仅 `MODE_AUTO/MDI`;paused 时 resume;非 idle 时 pause。 - `RESUME`:必须 `paused && MODE_AUTO/MDI`。 后续测试不要只断言按钮可点击,应断言 task/HAL status: - pause 后 `task.interpState=PAUSED` - pause 后 `task.taskPaused=true` - pause 后 `motion.paused=true` - resume 后恢复 `interpResumeState` - step 后 `singleStepping=true` ### 14.6 5 轴 XYZBC-TRT 特有要求 5 轴仿真不应把主按钮逻辑和 RTCP/kinematics 逻辑混在一起。建议分层: 1. task/motion 状态机:只负责开关机、home、run、pause、step。 2. kinematics/RTCP:只负责 XYZBC-TRT 位置变换和 TCP 显示。 3. interpreter/remap:负责 M428/M429/M430、switchkins、G-code 语义。 4. UI:只渲染状态并发送命令。 Run 前置条件除 LinuxCNC 通用条件外,还应检查: - 选定 profile 的 INI 已加载; - machine files 已 staged; - 当前 G-code 已 open 到 task/HAL session; - kinematics runtime 与 task/HAL runtime readiness 均为 true; - 如果程序依赖 switchkins/remap,HAL `motion.switchkins-type` 同步状态应可观测。 ## 15. 推荐验证矩阵 ### 15.1 LinuxCNC 行为矩阵 用本地 LinuxCNC 源码为基准建立 golden matrix: | 初始状态 | 命令 | 期望 | | --- | --- | --- | | ESTOP/MANUAL/IDLE | ESTOP button | `STATE_ESTOP_RESET` | | ESTOP_RESET/MANUAL/IDLE | Power | `STATE_ON` | | ESTOP/MANUAL/IDLE | Power | 不上电,走 OFF 分支 | | ON/MANUAL/IDLE | Home All | `joint.*.homing -> homed` | | ON/AUTO/IDLE + unhomed | Run | 拒绝,提示未 homed | | ON/AUTO/IDLE + homed | Run | `interpState=READING` | | ON/AUTO/READING | Pause | `interpState=PAUSED`、`motion.paused=true` | | ON/AUTO/PAUSED | Resume | 恢复 reading/waiting | | ON/AUTO/IDLE + homed | Step | run + pause,single step 准备 | | ON/AUTO/PAUSED + queue>0 | Step | `EMCMOT_STEP`,执行下一段后停 | ### 15.2 wasm-port 测试建议 新增或扩展: - `tests/wasm/node/verify_task_state_matrix.mjs` - `tests/wasm/node/verify_task_home_state_machine.mjs` - `tests/wasm/node/verify_task_pause_resume_step.mjs` 每个测试不要只看返回码,要读 `lctask_read_status_json()`: - task 字段; - motion 字段; - HAL pins; - events; - command echo。 ### 15.3 Web 测试建议 在现有 `verify_xyzbc_trt_web_app.mjs` 和 browser smoke 上补齐: - ESTOP/RESET/POWER 不允许绕过; - Home 有过程态; - Run 未 homed 拒绝; - Pause idle 拒绝; - Pause manual 拒绝; - Step idle 首次启动后暂停; - Step paused 推进一段; - Resume 清 single stepping; - Store 状态与 taskHalStatus 一致。 ## 16. 实施优先级 建议按以下顺序推进: 1. `wasm-port` 先消除 task/HAL runtime 中的 G-code 正则解析 fallback,要求 motion plan 必须来自 LinuxCNC interpreter canonical output。 2. `wasm-port` 把 `lctask_send_command_json()` 改为显式命令结构解析和 LinuxCNC 状态门禁表。 3. `wasm-port` 补齐 `task/motion/joint` 状态字段,使 `readStatus()` 接近 `linuxcnc.stat()`。 4. Web store 改为 taskHalStatus 投影源,减少直接改 `runState/machine` 的分支。 5. Web Home/Step 加过程态和真实 task/motion 状态断言。 6. 再继续推进 vendored `emctask.cc/taskintf.cc/command.c/homing.c` 的更直接编译或等价适配。 最终目标:Web 按钮只是 AXIS 的浏览器外壳,状态机和 CNC 语义来自 LinuxCNC 源码或其受控 WASM 适配层。