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

19 KiB
Raw Blame History

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.ccsrc/emc/task/emctask.ccsrc/emc/task/taskintf.cc
  • motion 层执行实时 enable、pause、step、home、homed/homing 记录,主要在 src/emc/motion/command.csrc/emc/motion/control.csrc/emc/motion/homing.c
  • AXIS 不直接相信按钮本地状态;它周期 stat.poll(),从 EMC_STAT 读回 task.statetask.modetask.interpStatetask.task_pausedmotion/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: ESTOPESTOP_RESETOFFON
  • EMC_TASK_MODE: MANUALAUTOMDI
  • EMC_TASK_INTERP: IDLEREADINGPAUSEDWAITING
  • EMC_TASK_EXEC: DONEWAITING_FOR_MOTIONWAITING_FOR_IOWAITING_FOR_MOTION_AND_IOERROR 等。

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_PAUSEEMC_TASK_PLAN_RESUMEEMC_TASK_PLAN_STEP 是无额外参数的 task plan 命令。

3. 状态记录与发布机制

3.1 AXIS UI 读状态

AXIS 的 stat 成员暴露在 emcmodule.cc

  • task_modetask_stateexec_stateinterp_stateread_linemotion_linecurrent_linefile 等字段直接映射 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_typeecho_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.cH[jno] 结构记录:

  • home_state: HOME_IDLEHOME_START、各类 search/latch/index/final move、HOME_FINISHEDHOME_ABORT
  • homing: 当前 joint 正在 homing。
  • homed: 当前 joint 已 homed。
  • homing_active: 全局是否仍有 homing 流程。

control.cget_homing(joint)get_homed(joint) 写入 joint_status->homingjoint_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_HOME joints。
  • emcAbortCleanup()emcTaskPlanSynch() 清理和同步解释器。

emcTaskSetState(ESTOP_RESET)

  • emcAuxEstopOff() 解除 IO 急停。
  • 停 coolant、abort task/io/spindle。
  • emcTaskPlanSynch()
  • motion 仍未 enable所以发布状态应落在 ESTOP_RESET

4.3 先决条件与状态记录

  • AXIS 层急停按钮始终可触发。
  • ESTOP 是安全覆盖命令,运动中也可以触发。
  • 点击后最终状态记录在 task.stateio.aux.estopmotion.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 要求 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.statemotion.traj.enabledmotion.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

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=1H[jno].homed=0
  • HOME_FINISHED 设置 H[jno].homing=0H[jno].homed=1home_state=HOME_IDLE
  • base_do_homing() 每 servo 周期推进状态机,并维护 homing_active

Web 对标:

  • Home 前必须 taskState=onmode=manualinterpState=idle、非 homing。
  • 需要记录 machine.homingmachine.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 或 INI NO_FORCE_HOMING=true
  • 程序文件已打开,或 task.file 可重新打开。

状态记录:

  • motion.traj.single_stepping = 0
  • stepping = 0steppingWait = 0
  • programStartLine = run_msg->line
  • task.interpState = READING
  • task.task_paused = 0
  • 执行期间 emcTaskExecute()interp_list 取命令,更新 task.currentLine,并通过 emcTrajSetMotionId(currentLine) 把当前行号传给 motion。
  • execState 通过 pre/post conditions 在 DONEWAITING_FOR_MOTIONWAITING_FOR_IOWAITING_FOR_MOTION_AND_IO 等之间切换。

Web 对标:

  • Run gate 必须以 taskState/mode/interpState/allHomed/noForceHoming/programOpen/taskHalRuntimeReady 为准。
  • Run 后记录 interpState=readingtaskPaused=falsesingleStepping=falsemotionStepping=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 = 0steppingWait = 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.pausedresume_inhibit=false
  • 必须同时记录 task 层 task_paused/interpState/interpResumeState 和 motion 层 paused

Web 对标:

  • taskPausedmotionPaused 都应入状态与 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.singleSteppingmachine.motionStepping
  • IDLE Step 应等价于启动 run 后暂停。
  • PAUSED Step 应按 motion id/line 推进一段后再次 pause。
  • Resume/Abort/ESTOP/OFF 必须清 singleSteppingmotionStepping

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_RESETinterpState=IDLE
上电 当前 STATE_ESTOP_RESET motion enable 输入必须 true task.state=ONmotion.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 -> falsehomed[]=trueallHomed=true
Home Joint 同 Home All且 joint/axis 选择合法 同上 对应 joint 的 homed[n] 更新
Run ensure_mode(AUTO),通常由 UI 限制 ON/IDLE ON、AUTO、IDLE、已 homed 或 no-force、程序打开 interpState=READINGtaskPaused=falsesingleStepping=false、line/motion id 更新
Pause 菜单要求 AUTO + READING/WAITING EMC_TASK_PLAN_PAUSE allowed in running states interpState=PAUSEDinterpResumeState=READING/WAITINGtaskPaused=truemotion.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 的对标要求

目标项目应以这些字段作为唯一状态事实源:

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不能静默切换。
  • RunStepMDI 要受 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 statuslinuxcnc/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