19 KiB
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,状态和命令通过linuxcncPython 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. 统一调用链
通用链路如下:
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_modevars.task_state <- self.stat.task_statevars.task_paused <- self.stat.task_pausedvars.interp_state <- self.stat.interp_statevars.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 共同推导:
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 调用链
入口:
axis.py commands.estop_clicked()
-> s.poll()
-> if s.task_state == STATE_ESTOP:
c.state(STATE_ESTOP_RESET)
else:
c.state(STATE_ESTOP)
快捷键绑定:
F1 -> commands.estop_clicked
C++ 扩展:
emcmodule.cc state()
-> EMC_TASK_SET_STATE.state = ESTOP 或 ESTOP_RESET
-> emcSendCommand()
task:
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_HOMEjoints。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 调用链
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)
快捷键:
F2 -> commands.onoff_clicked
后续仍走:
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要求 HALmotion.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-enabledHAL 输出、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:
axis.py home_all_joints()
-> if not manual_ok(): return
-> ensure_mode(MODE_MANUAL)
-> all_homed() 判断是否二次确认
-> go_home(-1)
单 joint:
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():
s.poll()
if 任一 s.joint[j]["homing"] 为真: 拒绝
set_motion_teleop(0)
c.home(num)
c.wait_complete()
C++:
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 调用链
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++ 扩展:
emcmodule.cc emcauto()
-> AUTO_RUN 带 line 参数
-> EMC_TASK_PLAN_RUN.line = line
-> emcSendCommand()
task:
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或 ININO_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:
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:
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++ 扩展:
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 调用链
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 周期检查:
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 的对标要求
目标项目应以这些字段作为唯一状态事实源:
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