# 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/MDI,paused 时走 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 off,motion 仍 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/MDI;paused 走 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`