Files
cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/19-20260707-AXIS按钮LinuxCNC-task-motion状态机制源码分析.md
2026-07-07 18:45:26 -04:00

561 lines
19 KiB
Markdown
Raw 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.
# 2026-07-07 AXIS 按钮 LinuxCNC task/motion 状态机制源码分析
## 1. 分析范围与结论
本文件针对 `/home/mes123456/cnc_wams/linuxcnc` 中 AXIS 主界面的“急停、上电、Home、执行、暂停、单步执行”做源码级调用链和状态机制分析并给 `/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan` 的完全对标提供状态字段依据。
核心结论:
- AXIS 主界面不是 C++,按钮回调在 `src/emc/usr_intf/axis/scripts/axis.py`,状态和命令通过 `linuxcnc` Python C++ 扩展进入 NML。
- C++ `task` 层是按钮命令的主状态机,主要在 `src/emc/task/emctaskmain.cc``src/emc/task/emctask.cc``src/emc/task/taskintf.cc`
- `motion` 层执行实时 enable、pause、step、home、homed/homing 记录,主要在 `src/emc/motion/command.c``src/emc/motion/control.c``src/emc/motion/homing.c`
- AXIS 不直接相信按钮本地状态;它周期 `stat.poll()`,从 `EMC_STAT` 读回 `task.state``task.mode``task.interpState``task.task_paused``motion`/`joint` 状态后刷新 UI。
## 2. 统一调用链
通用链路如下:
```text
AXIS Tk 按钮/快捷键
-> axis.py 回调
-> linuxcnc.command() / linuxcnc.stat()
-> src/emc/usr_intf/axis/extensions/emcmodule.cc
-> EMC_* NML 命令对象
-> milltask: src/emc/task/emctaskmain.cc emcTaskPlan()
-> emcTaskIssueCommand()
-> src/emc/task/emctask.cc 状态动作 或 src/emc/task/taskintf.cc motion 命令
-> src/emc/motion/command.c 实时 motion 命令处理
-> src/emc/motion/control.c 周期控制与状态输出
-> emcStatusBuffer->write(emcStatus)
-> AXIS stat.poll() 刷新按钮、DRO、状态栏
```
状态枚举来自 `src/emc/nml_intf/emc.hh`
- `EMC_TASK_STATE`: `ESTOP``ESTOP_RESET``OFF``ON`
- `EMC_TASK_MODE`: `MANUAL``AUTO``MDI`
- `EMC_TASK_INTERP`: `IDLE``READING``PAUSED``WAITING`
- `EMC_TASK_EXEC`: `DONE``WAITING_FOR_MOTION``WAITING_FOR_IO``WAITING_FOR_MOTION_AND_IO``ERROR` 等。
NML 命令对象来自 `src/emc/nml_intf/emc_nml.hh`
- `EMC_TASK_SET_STATE` 记录目标 task state。
- `EMC_TASK_SET_MODE` 记录目标 mode。
- `EMC_JOINT_HOME` 记录 joint 编号。
- `EMC_TASK_PLAN_RUN` 记录起始行 `line`
- `EMC_TASK_PLAN_PAUSE``EMC_TASK_PLAN_RESUME``EMC_TASK_PLAN_STEP` 是无额外参数的 task plan 命令。
## 3. 状态记录与发布机制
### 3.1 AXIS UI 读状态
AXIS 的 `stat` 成员暴露在 `emcmodule.cc`
- `task_mode``task_state``exec_state``interp_state``read_line``motion_line``current_line``file` 等字段直接映射 `EMC_STAT.task`
- `state()``mode()``home()``auto()` 分别把 Python 调用转换成 NML 命令发送。
`axis.py` 周期刷新中把读回状态写入 Tk 变量:
- `vars.task_mode <- self.stat.task_mode`
- `vars.task_state <- self.stat.task_state`
- `vars.task_paused <- self.stat.task_paused`
- `vars.interp_state <- self.stat.interp_state`
- `vars.interp_pause <- self.stat.paused`
这意味着 AXIS 按钮 active/disabled 不是按钮点击后本地硬改,而是以 task/motion 返回状态为准。
### 3.2 task 状态发布
`emctaskmain.cc` 主循环每周期:
- `emcMotionUpdate(&emcStatus->motion)` 读 motion 状态。
- `emcTaskUpdate(&emcStatus->task)` 更新 task mode/state。
- 设置 `emcStatus->task.command_type``echo_serial_number`
- 根据 task、motion、io 是否 `ERROR/DONE/EXEC` 写顶层 status。
- 最后 `emcStatusBuffer->write(emcStatus)` 发布给 GUI。
`emcTaskUpdate()` 最终由 `emctask.cc` 中的推导逻辑更新 task mode/state。task state 不是简单等于上次按钮命令,而是由 IO 急停与 motion enable 共同推导:
```text
io.aux.estop == true -> task.state = ESTOP
io.aux.estop == false && motion enabled false -> task.state = ESTOP_RESET/OFF 语义
io.aux.estop == false && motion enabled true -> task.state = ON
```
### 3.3 motion/homing 状态发布
motion 实时周期 `control.c` 执行:
- 读 homing 输入、处理 kinematics、probe、fault、operating mode。
- 在 free mode 下调用 `do_homing()`
- 输出 HAL pin 与 motion status。
Home 状态由 `homing.c``H[jno]` 结构记录:
- `home_state`: `HOME_IDLE``HOME_START`、各类 search/latch/index/final move、`HOME_FINISHED``HOME_ABORT`
- `homing`: 当前 joint 正在 homing。
- `homed`: 当前 joint 已 homed。
- `homing_active`: 全局是否仍有 homing 流程。
`control.c``get_homing(joint)``get_homed(joint)` 写入 `joint_status->homing``joint_status->homed`,供 task/GUI 读取。
## 4. 急停按钮
### 4.1 AXIS 调用链
入口:
```text
axis.py commands.estop_clicked()
-> s.poll()
-> if s.task_state == STATE_ESTOP:
c.state(STATE_ESTOP_RESET)
else:
c.state(STATE_ESTOP)
```
快捷键绑定:
```text
F1 -> commands.estop_clicked
```
C++ 扩展:
```text
emcmodule.cc state()
-> EMC_TASK_SET_STATE.state = ESTOP 或 ESTOP_RESET
-> emcSendCommand()
```
task
```text
emctaskmain.cc emcTaskPlan()
-> ESTOP/OFF/ESTOP_RESET/ON 各状态中 EMC_TASK_SET_STATE 都属于 immediate command
-> emcTaskIssueCommand()
-> emcTaskSetState()
```
### 4.2 C++ 状态动作
`emcTaskSetState(ESTOP)`
- `emcMotionAbort()` 中止 motion。
- `emcSpindleAbort()` 中止全部 spindle。
- `emcAuxEstopOn()` 置 IO 急停。
- `emcTrajDisable()` 禁用 trajectory/motion。
- `emcTaskAbort()` 清解释器/队列。
- `emcIoAbort(TASK_STATE_ESTOP)` 通知 IO。
- `emcJointUnhome(-2)` 只清 `VOLATILE_HOME` joints。
- `emcAbortCleanup()``emcTaskPlanSynch()` 清理和同步解释器。
`emcTaskSetState(ESTOP_RESET)`
- `emcAuxEstopOff()` 解除 IO 急停。
- 停 coolant、abort task/io/spindle。
- `emcTaskPlanSynch()`
- motion 仍未 enable所以发布状态应落在 `ESTOP_RESET`
### 4.3 先决条件与状态记录
- AXIS 层急停按钮始终可触发。
- ESTOP 是安全覆盖命令,运动中也可以触发。
- 点击后最终状态记录在 `task.state``io.aux.estop``motion.traj.enabled`、spindle/coolant 状态、volatile homed 标志。
- Web 对标时,`ESTOP` 必须清 `taskPaused/motionPaused/singleStepping/motionStepping`,速度归零,最终以 task/HAL status 回写为准。
## 5. 上电/下电按钮
### 5.1 AXIS 调用链
```text
axis.py commands.onoff_clicked()
-> s.poll()
-> if s.task_state == STATE_ESTOP_RESET:
c.state(STATE_ON)
可选 HOMING_PROMPT 后 commands.home_all_joints()
else:
c.state(STATE_OFF)
```
快捷键:
```text
F2 -> commands.onoff_clicked
```
后续仍走:
```text
emcmodule.cc state()
-> EMC_TASK_SET_STATE
-> emctaskmain.cc emcTaskIssueCommand()
-> emctask.cc emcTaskSetState()
```
### 5.2 C++ 状态动作
`emcTaskSetState(ON)`
- `emcTrajEnable()` 下发 motion enable。
- `emcCoolantFloodOff()`
- motion 侧 `EMCMOT_ENABLE` 要求 HAL `motion.enable` 输入为 true否则报错。
- 真正 `task.state=ON` 要等 motion enabled 后由 task update 推导。
`emcTaskSetState(OFF)`
- abort motion/spindle/io/task。
- `emcTrajDisable()`
- `emcJointUnhome(-2)` 清 volatile home。
- `emcTaskPlanSynch()`
### 5.3 先决条件与状态记录
- 上电只允许从 `STATE_ESTOP_RESET` 进入 `STATE_ON`
- 当前为 `STATE_ON` 时同一按钮执行下电。
- 当前为 `STATE_ESTOP` 时 AXIS UI 通常禁用上电;如果误触发 Python 分支也会发送 OFF而不是 ON。
- 状态记录必须包含 `task.state``motion.traj.enabled``motion.motion-enabled` HAL 输出、spindle/coolant abort 后状态。
Web 对标:
- `taskState=estop` 时 Power 应禁止,提示先解除急停。
- `taskState=estop-reset` 时 Power 发 `EMC_TASK_SET_STATE ON`
- `taskState=on` 时 Power 发 `EMC_TASK_SET_STATE OFF` 并清运行/暂停/单步。
## 6. Home 按钮
### 6.1 AXIS 调用链
Home All
```text
axis.py home_all_joints()
-> if not manual_ok(): return
-> ensure_mode(MODE_MANUAL)
-> all_homed() 判断是否二次确认
-> go_home(-1)
```
单 joint
```text
axis.py home_joint()
-> if not manual_ok(): return
-> 由 UI 选择解析 joint 编号
-> 非 identity kinematics 下坐标轴 Home 被拒绝,要求 joint mode
-> ensure_mode(MODE_MANUAL)
-> go_home(jnum)
```
`go_home()`
```text
s.poll()
if 任一 s.joint[j]["homing"] 为真: 拒绝
set_motion_teleop(0)
c.home(num)
c.wait_complete()
```
C++
```text
emcmodule.cc home()
-> EMC_JOINT_HOME.joint = -1 或 n
-> emctaskmain.cc emcTaskIssueCommand()
-> taskintf.cc emcJointHome()
-> EMCMOT_JOINT_HOME
-> motion/command.c
-> homing.c do_home_joint()/do_homing()
```
### 6.2 motion 硬条件
`motion/command.c` 处理 `EMCMOT_JOINT_HOME` 时要求:
- `motion_state == EMCMOT_MOTION_FREE`,否则报 “must be in joint mode to home”。
- `motion.homing-inhibit` 为 false。
- `get_homing_is_active()` 为 false即不能在 homing 未完成时再次 Home。
- `GET_MOTION_ENABLE_FLAG()` 为 true未 enable 时不会启动 homing。
- `joint=-1` 表示 Home All。
### 6.3 状态记录
`homing.c` 中:
- `do_home_joint(jno)` 把目标 joint 的 `home_state` 置为 `HOME_START`;负序列时会把同组 joint 一起置为 `HOME_START`
- `HOME_START` 设置 `H[jno].homing=1``H[jno].homed=0`
- `HOME_FINISHED` 设置 `H[jno].homing=0``H[jno].homed=1``home_state=HOME_IDLE`
- `base_do_homing()` 每 servo 周期推进状态机,并维护 `homing_active`
Web 对标:
- Home 前必须 `taskState=on``mode=manual``interpState=idle`、非 homing。
- 需要记录 `machine.homing``machine.homed[]``machine.allHomed`
- 回零期间禁止 Jog、Run、Step、再次 Home。
- Home All 与单 Home 必须使用 `EMC_JOINT_HOME joint=-1|n` 的语义。
## 7. 执行 Run 按钮
### 7.1 AXIS 调用链
```text
axis.py task_run()
-> run_warn()/reload_file()
-> program_start_line_last = program_start_line
-> ensure_mode(MODE_AUTO)
-> c.auto(AUTO_RUN, program_start_line)
-> program_start_line = 0
```
C++ 扩展:
```text
emcmodule.cc emcauto()
-> AUTO_RUN 带 line 参数
-> EMC_TASK_PLAN_RUN.line = line
-> emcSendCommand()
```
task
```text
emctaskmain.cc emcTaskPlan()
-> ON + AUTO + IDLE 时允许 EMC_TASK_PLAN_RUN
-> emcTaskIssueCommand()
-> all_homed() && !no_force_homing 检查
-> emcTaskPlanOpen()
-> programStartLine = run_msg->line
-> task.interpState = READING
-> task.task_paused = 0
```
### 7.2 C++ 硬条件与状态
硬条件:
- `task.state == ON`
- `task.mode == AUTO`
- 新 Run 正常要求 `interpState == IDLE`
- `all_homed()==true` 或 INI `NO_FORCE_HOMING=true`
- 程序文件已打开,或 `task.file` 可重新打开。
状态记录:
- `motion.traj.single_stepping = 0`
- `stepping = 0``steppingWait = 0`
- `programStartLine = run_msg->line`
- `task.interpState = READING`
- `task.task_paused = 0`
- 执行期间 `emcTaskExecute()``interp_list` 取命令,更新 `task.currentLine`,并通过 `emcTrajSetMotionId(currentLine)` 把当前行号传给 motion。
- `execState` 通过 pre/post conditions 在 `DONE``WAITING_FOR_MOTION``WAITING_FOR_IO``WAITING_FOR_MOTION_AND_IO` 等之间切换。
Web 对标:
- Run gate 必须以 `taskState/mode/interpState/allHomed/noForceHoming/programOpen/taskHalRuntimeReady` 为准。
- Run 后记录 `interpState=reading``taskPaused=false``singleStepping=false``motionStepping=false`
- active line 应尽量使用 task/HAL status 的 line/motion id而不是只靠前端 sample index。
## 8. 暂停与恢复按钮
### 8.1 AXIS 调用链
菜单 Pause
```text
axis.py task_pause()
-> 要求 task_mode == MODE_AUTO
-> 要求 interp_state in {INTERP_READING, INTERP_WAITING}
-> ensure_mode(MODE_AUTO)
-> c.auto(AUTO_PAUSE)
```
工具栏 Pause/Resume
```text
axis.py task_pauseresume()
-> 要求 task_mode in {MODE_AUTO, MODE_MDI}
-> if s.paused:
如果 resume_inhibit 为真则拒绝
c.auto(AUTO_RESUME)
elif s.interp_state != INTERP_IDLE:
c.auto(AUTO_PAUSE)
```
C++ 扩展:
```text
AUTO_PAUSE -> EMC_TASK_PLAN_PAUSE
AUTO_RESUME -> EMC_TASK_PLAN_RESUME
```
### 8.2 task/motion 状态动作
`EMC_TASK_PLAN_PAUSE`
- `emcTrajPause()` 下发 `EMCMOT_PAUSE`
- 若当前不是 `PAUSED`,保存 `interpResumeState = 当前 interpState`
- `task.interpState = PAUSED`
- `task.task_paused = 1`
motion `EMCMOT_PAUSE`
- `tpPause(&coord_tp)`
- `motion.paused = 1`
`EMC_TASK_PLAN_RESUME`
- `emcTrajResume()` 下发 `EMCMOT_RESUME`
- `task.interpState = interpResumeState`
- `task.task_paused = 0`
- `motion.traj.single_stepping = 0`
- `stepping = 0``steppingWait = 0`
motion `EMCMOT_RESUME`
- `motion.stepping = 0`
- `tpResume(&coord_tp)`
- `motion.paused = 0`
### 8.3 先决条件与状态记录
- 菜单 Pause 比工具栏 Pause/Resume 严格,只允许 `AUTO + READING/WAITING`
- 工具栏 Pause/Resume 允许 AUTO/MDIpaused 时走 Resume非 idle 时走 Pause。
- Resume 需要 `s.paused``resume_inhibit=false`
- 必须同时记录 task 层 `task_paused/interpState/interpResumeState` 和 motion 层 `paused`
Web 对标:
- `taskPaused``motionPaused` 都应入状态与 evidence。
- 暂停期间位置、速度、activeLine、tool pose 必须冻结。
- Resume 不应固定写 `reading`,应恢复 `interpResumeState`
## 9. 单步 Step 按钮
### 9.1 AXIS 调用链
```text
axis.py task_step()
-> 如果不是 AUTO 或 interp_state != IDLE先 run_warn()
-> ensure_mode(MODE_AUTO)
-> c.auto(AUTO_STEP)
-> emcmodule.cc emcauto()
-> EMC_TASK_PLAN_STEP
-> emctaskmain.cc emcTaskPlan()
```
### 9.2 task 分状态处理
`AUTO + IDLE`
- task 把第一次 Step 视为从头 Run构造 `taskPlanRunCmd.line = 0`
- `emcTaskIssueCommand(&taskPlanRunCmd)`
- 随即 `emcTrajPause()`
- 保存 `interpResumeState`
- `task.interpState = PAUSED`
- `task.task_paused = 1`
`AUTO + READING/WAITING`
- `motion.traj.single_stepping = 1`
- `stepping = 1`
- `steppingWait = 0`
`AUTO + PAUSED`
- 同样设置 single stepping。
- 如果 `motion.traj.paused && motion.traj.queue > 0`,调用 `emcTrajStep()`
- 否则把 `task.interpState` 恢复为 `interpResumeState` 以继续读下一步。
- `task.task_paused = 1`
### 9.3 motion 单步机制
`EMCMOT_STEP`
- 只有 `motion.paused` 时才执行。
- 保存 `idForStep = 当前 motion id`
- `motion.stepping = 1`
- `tpResume()` 暂时放行。
- `motion.paused` 保持为 1 的语义状态。
`control.c` 周期检查:
```text
if motion.stepping && idForStep != current motion id:
tpPause()
motion.stepping = 0
motion.paused = 1
```
也就是说 LinuxCNC 的 Step 是“放行到下一个 motion id/line 后再次暂停”,不是前端固定推进一个采样点。
Web 对标:
- 需要同时记录 `machine.singleStepping``machine.motionStepping`
- IDLE Step 应等价于启动 run 后暂停。
- PAUSED Step 应按 motion id/line 推进一段后再次 pause。
- Resume/Abort/ESTOP/OFF 必须清 `singleStepping``motionStepping`
## 10. 每个按钮的先决条件汇总
| 按钮 | AXIS/Python 先决条件 | C++/motion 硬条件 | 执行后状态记录 |
|---|---|---|---|
| 急停 | 始终可触发 | `EMC_TASK_SET_STATE` immediate安全覆盖 | `task.state=ESTOP`、motion disabled、spindle/coolant abort、pause/step 清理、volatile home 可清 |
| 解除急停 | 当前 `STATE_ESTOP` 时 F1 分支 | IO estop offmotion 仍 disabled | `task.state=ESTOP_RESET``interpState=IDLE` |
| 上电 | 当前 `STATE_ESTOP_RESET` | motion enable 输入必须 true | `task.state=ON``motion.enabled=true` |
| 下电 | 当前非 `STATE_ESTOP_RESET` 的 on/off 分支 | abort + disable | `task.state=ESTOP_RESET/OFF语义`、运行/暂停/step 清理 |
| Home All | `manual_ok()`,切 `MODE_MANUAL`,非 homing | free/joint mode、motion enabled、homing-inhibit false、无 homing active | `homing=true -> false``homed[]=true``allHomed=true` |
| Home Joint | 同 Home All且 joint/axis 选择合法 | 同上 | 对应 joint 的 `homed[n]` 更新 |
| Run | `ensure_mode(AUTO)`,通常由 UI 限制 ON/IDLE | ON、AUTO、IDLE、已 homed 或 no-force、程序打开 | `interpState=READING``taskPaused=false``singleStepping=false`、line/motion id 更新 |
| Pause | 菜单要求 AUTO + READING/WAITING | `EMC_TASK_PLAN_PAUSE` allowed in running states | `interpState=PAUSED``interpResumeState=READING/WAITING``taskPaused=true``motion.paused=true` |
| Pause/Resume | 工具栏要求 AUTO/MDIpaused 走 Resume非 idle 走 Pause | resume 走 `EMCMOT_RESUME` | Resume 后 `interpState=interpResumeState`、pause/step 清理 |
| Step | 切 AUTO非 idle 时先 run_warn | AUTO 分状态motion step 只在 paused 时放行 | IDLE step 启动后暂停paused step 放行到下个 motion id 后再暂停 |
## 11. 对 `web-rtcp-5axis-xyzbc-trt-sim-plan` 的对标要求
目标项目应以这些字段作为唯一状态事实源:
```js
machine: {
taskState: "estop" | "estop-reset" | "on",
mode: "manual" | "auto" | "mdi",
interpState: "idle" | "reading" | "paused" | "waiting",
interpResumeState: "idle" | "reading" | "waiting",
taskPaused: boolean,
motionPaused: boolean,
singleStepping: boolean,
motionStepping: boolean,
motionEnabled: boolean,
homing: boolean,
homed: boolean[],
allHomed: boolean,
noForceHoming: boolean,
resumeInhibit: boolean
}
```
当前项目中的 `app/src/state/linuxcnc-task-policy.js` 已经是正确的集中门禁位置;`app/src/state/store.js` 负责实际状态写入;`app/src/runtime/linuxcnc-task-hal-runtime.js` 负责把 Task/HAL/WASM status 标准化。后续不能让 UI 组件分散判断按钮可用性。
必须保持的 LinuxCNC 语义:
- `taskState` 由急停和 motion enable 派生,不能只由 `powerOn` 派生。
- `mode` 切出 AUTO 时,如果解释器非 idle 必须拒绝或先 abort不能静默切换。
- `Run``Step``MDI` 要受 `allHomed/noForceHoming` 控制。
- `Pause` 要区分 task paused 与 motion paused且冻结执行位置。
- `Step` 要向 motion id/line 语义靠拢,而不是永久使用固定 sample 步长。
- `ESTOP/OFF/ABORT/RESUME` 都要清理 pause/step 残留状态。
## 12. 源码索引
- AXIS 按钮与快捷键:`linuxcnc/src/emc/usr_intf/axis/scripts/axis.py`
- Python C++ 扩展:`linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc`
- NML 枚举:`linuxcnc/src/emc/nml_intf/emc.hh`
- NML 命令与 task status`linuxcnc/src/emc/nml_intf/emc_nml.hh`
- task 主状态机:`linuxcnc/src/emc/task/emctaskmain.cc`
- task mode/state 动作:`linuxcnc/src/emc/task/emctask.cc`
- task 到 motion 命令封装:`linuxcnc/src/emc/task/taskintf.cc`
- motion 命令处理:`linuxcnc/src/emc/motion/command.c`
- motion 周期控制和状态输出:`linuxcnc/src/emc/motion/control.c`
- homing 状态机:`linuxcnc/src/emc/motion/homing.c`