Files
cnc_wams/项目分析/AXIS主控制按钮调用链与WASM完善指南.md
2026-07-07 16:46:39 -04:00

930 lines
30 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 onreset 分支 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/remapHAL `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 + pausesingle 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 适配层。