docs: record pause wasm implementation plan
This commit is contained in:
703
web-rtcp-5axis-xyzbc-trt-sim-plan/doc/AXIS主控制按钮功能先决条件与状态影响详解.md
Normal file
703
web-rtcp-5axis-xyzbc-trt-sim-plan/doc/AXIS主控制按钮功能先决条件与状态影响详解.md
Normal file
@@ -0,0 +1,703 @@
|
||||
# AXIS 主控制按钮功能、先决条件与机床状态影响详解
|
||||
|
||||
本文档单独整理 `5axis-xyzbc-trt-sim` 配置在 AXIS 界面中常用主控制按钮的执行条件、状态影响和对应程序链路。目标配置为:
|
||||
|
||||
```text
|
||||
configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini
|
||||
```
|
||||
|
||||
该配置使用:
|
||||
|
||||
- GUI:`[DISPLAY] DISPLAY = axis`
|
||||
- 任务进程:`[TASK] TASK = milltask`
|
||||
- HAL 仿真闭环:`[HAL] HALFILE = LIB:basic_sim.tcl`
|
||||
- 默认打开程序:`[DISPLAY] OPEN_FILE = ./demos/xyzbc_switchkins.ngc`
|
||||
- 运动学:`[KINS] KINEMATICS = xyzbc-trt-kins sparm=identityfirst`
|
||||
- 关节数:`[KINS] JOINTS = 5`
|
||||
|
||||
## 1. 状态和模式基础
|
||||
|
||||
AXIS 主按钮最终通过 Python `linuxcnc.command()` 对象发送 NML 命令给 `milltask`。常见状态如下:
|
||||
|
||||
| 名称 | LinuxCNC 常量 | 含义 |
|
||||
|---|---|---|
|
||||
| 急停中 | `STATE_ESTOP` | 急停有效,机床关闭,运动禁止 |
|
||||
| 急停已解除但未上电 | `STATE_ESTOP_RESET` | 急停链释放,机床仍未使能 |
|
||||
| 下电 | `STATE_OFF` | 机床未使能;Python 扩展说明中等价于急停复位后的 off 状态 |
|
||||
| 上电 | `STATE_ON` | 急停释放且机床使能,可回零、MDI、自动运行 |
|
||||
|
||||
常见任务模式如下:
|
||||
|
||||
| 名称 | LinuxCNC 常量 | 用途 |
|
||||
|---|---|---|
|
||||
| 手动 | `MODE_MANUAL` | 回零、点动、手动操作 |
|
||||
| MDI | `MODE_MDI` | 执行单行 MDI 指令 |
|
||||
| 自动 | `MODE_AUTO` | 执行已装载 G-code 程序 |
|
||||
|
||||
核心源码位置:
|
||||
|
||||
```text
|
||||
src/emc/usr_intf/axis/scripts/axis.py
|
||||
src/emc/usr_intf/axis/extensions/emcmodule.cc
|
||||
src/emc/task/emctaskmain.cc
|
||||
src/emc/task/emctask.cc
|
||||
src/emc/task/taskintf.cc
|
||||
src/emc/nml_intf/emc_nml.hh
|
||||
```
|
||||
|
||||
## 2. 按钮总表
|
||||
|
||||
| 按钮/动作 | AXIS 回调函数 | Python API | NML 命令 | 主要先决条件 | 主要状态影响 |
|
||||
|---|---|---|---|---|---|
|
||||
| 急停 | `estop_clicked()` | `c.state(STATE_ESTOP)` | `EMC_TASK_SET_STATE` | LinuxCNC 已运行;通常任意非急停状态都可触发 | 中止运动、主轴、冷却;使能关闭;进入 `STATE_ESTOP` |
|
||||
| 解除急停 | `estop_clicked()` | `c.state(STATE_ESTOP_RESET)` | `EMC_TASK_SET_STATE` | 当前为 `STATE_ESTOP` | 释放急停链;仍未上电;进入 `STATE_ESTOP_RESET` |
|
||||
| 上电 | `onoff_clicked()` | `c.state(STATE_ON)` | `EMC_TASK_SET_STATE` | 当前为 `STATE_ESTOP_RESET` | 轨迹/运动使能;进入 `STATE_ON` |
|
||||
| Home | `home_all_joints()` / `home_joint()` | `c.home(joint)` | `EMC_JOINT_HOME` | `STATE_ON`;解释器空闲;手动模式;没有正在回零的关节 | 指定关节或全部关节回零,`homed[]` 变为 true |
|
||||
| 执行程序 | `task_run()` | `c.auto(AUTO_RUN, line)` | `EMC_TASK_PLAN_RUN` | 上电、已回零、程序已打开、通过运行前检查 | 切入自动模式,解释器进入 `READING`,开始运行 G-code |
|
||||
| 暂停 | `task_pause()` | `c.auto(AUTO_PAUSE)` | `EMC_TASK_PLAN_PAUSE` | 自动模式;解释器处于 `READING` 或 `WAITING` | 轨迹暂停,解释器状态保存为 `PAUSED`,`task_paused=1` |
|
||||
| 恢复暂停 | `task_resume()` / `task_pauseresume()` | `c.auto(AUTO_RESUME)` | `EMC_TASK_PLAN_RESUME` | 当前已暂停;自动或 MDI 模式;未被 `resume-inhibit` 禁止 | 轨迹恢复,解释器返回暂停前状态,`task_paused=0` |
|
||||
| 单步执行 | `task_step()` | `c.auto(AUTO_STEP)` | `EMC_TASK_PLAN_STEP` | 自动模式且解释器空闲;若不是空闲会先做运行警告检查 | 打开 single step,一次推进一个解释/运动步骤 |
|
||||
|
||||
## 3. 急停
|
||||
|
||||
### 3.1 执行入口
|
||||
|
||||
AXIS 中急停按钮和 `F1` 键使用同一个回调:
|
||||
|
||||
```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`,就是解除急停。
|
||||
|
||||
### 3.2 先决条件
|
||||
|
||||
- LinuxCNC 已启动,AXIS 可读取状态并可向 command channel 发送命令。
|
||||
- 不要求当前在手动、MDI 或自动模式。
|
||||
- 不要求程序空闲;急停是最高优先级安全动作,可在运动中触发。
|
||||
|
||||
### 3.3 机床状态影响
|
||||
|
||||
急停发送:
|
||||
|
||||
```text
|
||||
c.state(linuxcnc.STATE_ESTOP)
|
||||
```
|
||||
|
||||
Python 扩展生成:
|
||||
|
||||
```text
|
||||
EMC_TASK_SET_STATE(state = ESTOP)
|
||||
```
|
||||
|
||||
`milltask` 调用:
|
||||
|
||||
```text
|
||||
emcTaskSetState(EMC_TASK_STATE::ESTOP)
|
||||
```
|
||||
|
||||
主要影响:
|
||||
|
||||
- `emcMotionAbort()`:立即中止当前运动。
|
||||
- `emcSpindleAbort()`:中止主轴。
|
||||
- `emcAuxEstopOn()`:置 IO 急停。
|
||||
- `emcTrajDisable()`:关闭轨迹/运动使能。
|
||||
- `emcCoolantFloodOff()`:关闭冷却。
|
||||
- `emcTaskAbort()`:中止任务层正在执行的程序/MDI。
|
||||
- `emcIoAbort(TASK_STATE_ESTOP)`:通知 IO 层急停原因。
|
||||
- `emcJointUnhome(-2)`:仅清除配置为 volatile home 的关节回零状态。
|
||||
- `emcTaskPlanSynch()`:同步解释器/任务计划状态。
|
||||
|
||||
在本仿真配置中,`basic_sim.tcl` 把 `motion.motion-enabled` 接到仿真伺服闭环选择信号。急停后 motion disabled,仿真反馈保持当前位置,不再跟随新的 `joint.N.motor-pos-cmd`。
|
||||
|
||||
## 4. 解除急停
|
||||
|
||||
### 4.1 执行入口
|
||||
|
||||
仍然是 AXIS 的 `estop_clicked()`。当前状态为 `STATE_ESTOP` 时,按钮执行:
|
||||
|
||||
```text
|
||||
c.state(linuxcnc.STATE_ESTOP_RESET)
|
||||
```
|
||||
|
||||
### 4.2 先决条件
|
||||
|
||||
- 当前必须处于 `STATE_ESTOP`,否则同一个按钮会变成“急停”动作。
|
||||
- 外部急停链、HAL 急停反馈需要允许复位。本仿真中 `basic_sim.tcl` 使用回环:
|
||||
|
||||
```text
|
||||
iocontrol.0.user-enable-out -> iocontrol.0.emc-enable-in
|
||||
```
|
||||
|
||||
所以没有真实硬件急停链阻塞。
|
||||
|
||||
### 4.3 机床状态影响
|
||||
|
||||
`emcTaskSetState(ESTOP_RESET)` 主要执行:
|
||||
|
||||
- `emcAuxEstopOff()`:释放急停。
|
||||
- `emcCoolantFloodOff()`:保持冷却关闭。
|
||||
- `emcTaskAbort()`:清理任务执行状态。
|
||||
- `emcIoAbort(TASK_STATE_ESTOP_RESET)`:通知 IO 层状态变化。
|
||||
- `emcSpindleAbort()`:确保主轴停止。
|
||||
- `emcAbortCleanup()`:清理 abort 状态。
|
||||
- `emcTaskPlanSynch()`:同步解释器。
|
||||
|
||||
解除急停后机床仍然没有上电,状态是:
|
||||
|
||||
```text
|
||||
STATE_ESTOP_RESET
|
||||
```
|
||||
|
||||
此时不能执行 Home、MDI 或自动运行,需要再按“上电”。
|
||||
|
||||
## 5. 上电
|
||||
|
||||
### 5.1 执行入口
|
||||
|
||||
AXIS 中上电/下电按钮和 `F2` 键使用:
|
||||
|
||||
```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` 时执行上电;否则执行下电。
|
||||
|
||||
### 5.2 先决条件
|
||||
|
||||
- 必须先解除急停,当前状态为 `STATE_ESTOP_RESET`。
|
||||
- 如果当前仍为 `STATE_ESTOP`,点击该按钮不会上电,而会进入 `STATE_OFF` 分支,实际无法达到可运动状态。
|
||||
|
||||
### 5.3 机床状态影响
|
||||
|
||||
上电发送:
|
||||
|
||||
```text
|
||||
c.state(linuxcnc.STATE_ON)
|
||||
```
|
||||
|
||||
`emcTaskSetState(ON)` 主要执行:
|
||||
|
||||
- `emcTrajEnable()`:向实时 motion 发送 `EMCMOT_ENABLE`。
|
||||
- `emcCoolantFloodOff()`:确保冷却默认关闭。
|
||||
|
||||
上电后:
|
||||
|
||||
- `task_state = STATE_ON`
|
||||
- 运动系统使能。
|
||||
- `motion.motion-enabled` 为真。
|
||||
- 本仿真 HAL 中各关节 PID pass-through 被允许,`joint.N.motor-pos-cmd` 可通过仿真闭环更新到 `joint.N.motor-pos-fb`。
|
||||
|
||||
### 5.4 下电补充
|
||||
|
||||
如果机器已经不是 `STATE_ESTOP_RESET`,同一个按钮会发送:
|
||||
|
||||
```text
|
||||
c.state(linuxcnc.STATE_OFF)
|
||||
```
|
||||
|
||||
下电影响包括中止运动、关闭轨迹使能、通知 IO、清理任务、同步解释器,并清除 volatile home 关节的回零状态。
|
||||
|
||||
## 6. Home
|
||||
|
||||
### 6.1 执行入口
|
||||
|
||||
本配置 5 个关节都设置了:
|
||||
|
||||
```ini
|
||||
HOME_SEARCH_VEL = 0
|
||||
HOME_SEQUENCE = 0
|
||||
```
|
||||
|
||||
AXIS 会显示 `Home All` 按钮。按钮执行:
|
||||
|
||||
```python
|
||||
def home_all_joints(event=None):
|
||||
if not manual_ok(): return
|
||||
ensure_mode(linuxcnc.MODE_MANUAL)
|
||||
...
|
||||
go_home(-1)
|
||||
```
|
||||
|
||||
`Home` 键可执行当前选择关节的 `home_joint()`,`Ctrl-Home` 执行全部回零。
|
||||
|
||||
`go_home()` 内部执行:
|
||||
|
||||
```python
|
||||
set_motion_teleop(0)
|
||||
c.home(num)
|
||||
c.wait_complete()
|
||||
```
|
||||
|
||||
其中 `num = -1` 表示全部关节。
|
||||
|
||||
### 6.2 先决条件
|
||||
|
||||
Home All 的主要条件:
|
||||
|
||||
- `manual_ok()` 为真:
|
||||
- `task_state == STATE_ON`
|
||||
- 解释器空闲,或者 MDI 队列仍允许用户动作
|
||||
- AXIS 会切换到 `MODE_MANUAL`。
|
||||
- 当前没有任何关节正在回零;`go_home()` 会检查 `s.joint[j]["homing"]`。
|
||||
- 如果已经全部回零,AXIS 会弹出确认提示。
|
||||
|
||||
单个关节 Home 的额外条件:
|
||||
|
||||
- 如果选择的是坐标轴字母,并且当前运动学不是 identity,AXIS 会提示使用 joint mode 回零并拒绝直接按轴回零。
|
||||
- 对重复坐标字母配置,AXIS 禁止按轴单独回零。
|
||||
|
||||
本配置启动默认是 `sparm=identityfirst`,即 `switchkins-type = 0` 为 identity,适合先回零。
|
||||
|
||||
### 6.3 机床状态影响
|
||||
|
||||
Home 发送:
|
||||
|
||||
```text
|
||||
c.home(joint)
|
||||
```
|
||||
|
||||
Python 扩展生成:
|
||||
|
||||
```text
|
||||
EMC_JOINT_HOME(joint = -1 或具体关节号)
|
||||
```
|
||||
|
||||
`milltask` 调用:
|
||||
|
||||
```text
|
||||
emcJointHome(joint)
|
||||
```
|
||||
|
||||
`taskintf.cc` 再向实时 motion 发送:
|
||||
|
||||
```text
|
||||
EMCMOT_JOINT_HOME
|
||||
```
|
||||
|
||||
本仿真使用 `basic_sim.tcl` 的 `simulated_home`,为每个关节建立:
|
||||
|
||||
```text
|
||||
joint.N.home-sw-in
|
||||
```
|
||||
|
||||
由于 `HOME_SEARCH_VEL = 0`,这是仿真中的立即/简化回零方式。完成后:
|
||||
|
||||
- 对应 `s.homed[N]` 变为 true。
|
||||
- `Home All` 后 5 个关节均为已回零。
|
||||
- 只有已回零后,普通配置才允许自动运行程序;`emctaskmain.cc` 中 `EMC_TASK_PLAN_RUN` 会检查 `all_homed()`。
|
||||
|
||||
## 7. 执行程序
|
||||
|
||||
### 7.1 执行入口
|
||||
|
||||
AXIS 运行按钮和 `r` 键执行:
|
||||
|
||||
```python
|
||||
def task_run(*event):
|
||||
res = run_warn()
|
||||
...
|
||||
ensure_mode(linuxcnc.MODE_AUTO)
|
||||
c.auto(linuxcnc.AUTO_RUN, program_start_line)
|
||||
```
|
||||
|
||||
当前配置启动时自动打开:
|
||||
|
||||
```text
|
||||
configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzbc_switchkins.ngc
|
||||
```
|
||||
|
||||
该文件内容很短:
|
||||
|
||||
```ngc
|
||||
o<xyzbc_switchkins_sub> call [10] [5] [10][1000][3][0][20][45][20]
|
||||
m2
|
||||
```
|
||||
|
||||
实际加工/演示逻辑在:
|
||||
|
||||
```text
|
||||
configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/xyzbc_switchkins_sub.ngc
|
||||
configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/helix_bc.ngc
|
||||
```
|
||||
|
||||
### 7.2 先决条件
|
||||
|
||||
AXIS 层:
|
||||
|
||||
- 程序已经打开。
|
||||
- `run_warn()` 运行前检查通过,或用户选择忽略软限位警告继续运行。
|
||||
- AXIS 能切换到 `MODE_AUTO`。
|
||||
|
||||
任务层:
|
||||
|
||||
- 机床需要处于可运行状态,通常为 `STATE_ON`。
|
||||
- 必须已经回零,除非配置关闭强制回零。`emctaskmain.cc` 对 `EMC_TASK_PLAN_RUN` 检查:
|
||||
|
||||
```text
|
||||
if (!all_homed() && !no_force_homing) {
|
||||
"Can't run a program when not homed"
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 机床状态影响
|
||||
|
||||
运行按钮发送:
|
||||
|
||||
```text
|
||||
c.auto(linuxcnc.AUTO_RUN, program_start_line)
|
||||
```
|
||||
|
||||
Python 扩展生成:
|
||||
|
||||
```text
|
||||
EMC_TASK_PLAN_RUN(line = program_start_line)
|
||||
```
|
||||
|
||||
任务层影响:
|
||||
|
||||
- 清除 single stepping:
|
||||
- `motion.traj.single_stepping = 0`
|
||||
- `stepping = 0`
|
||||
- 如果 task plan 尚未打开且有文件名,则打开当前 G-code 文件。
|
||||
- 设置 `programStartLine`。
|
||||
- 设置解释器状态:
|
||||
|
||||
```text
|
||||
interpState = READING
|
||||
task_paused = 0
|
||||
```
|
||||
|
||||
之后解释器读取 G-code,生成 canonical commands,经 `taskintf` 下发给 realtime motion,motion 在伺服周期中计算轨迹和各 joint 命令。
|
||||
|
||||
### 7.4 对当前演示程序的影响
|
||||
|
||||
`xyzbc_switchkins_sub.ngc` 会在四个象限重复执行:
|
||||
|
||||
1. `M429` 切换到 identity。
|
||||
2. `G53 G0 X0 Y0 Z... B0 C0` 回到机床坐标安全姿态。
|
||||
3. `G10 L20 P0 ...` 设置 G54。
|
||||
4. 移动到当前象限中心。
|
||||
5. 调用 `helix_bc` 执行带 B/C 姿态的螺旋轨迹。
|
||||
|
||||
其中 `M428/M429/M430` 是 remap:
|
||||
|
||||
- `M429`:`motion.switchkins-type = 0`,identity。
|
||||
- `M428`:`motion.switchkins-type = 1`,xyzbc TRT。
|
||||
- `M430`:`motion.switchkins-type = 2`,userk。
|
||||
|
||||
remap 通过:
|
||||
|
||||
```ngc
|
||||
M68 E3 Q#<kinstype>
|
||||
M66 E0 L0
|
||||
```
|
||||
|
||||
把数值写入 `motion.analog-out-03`,再由 INI 中 HAL 网络连接到:
|
||||
|
||||
```text
|
||||
motion.switchkins-type
|
||||
```
|
||||
|
||||
## 8. 暂停
|
||||
|
||||
### 8.1 执行入口
|
||||
|
||||
AXIS 暂停按钮和 `p` 键执行:
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
### 8.2 先决条件
|
||||
|
||||
- 当前必须在 `MODE_AUTO`。
|
||||
- 解释器状态必须是:
|
||||
- `INTERP_READING`:正在读程序
|
||||
- `INTERP_WAITING`:等待运动或 IO 完成
|
||||
- 如果程序已经空闲、已经暂停、处于手动或 MDI,AXIS 直接返回,不发送暂停命令。
|
||||
|
||||
### 8.3 机床状态影响
|
||||
|
||||
暂停发送:
|
||||
|
||||
```text
|
||||
EMC_TASK_PLAN_PAUSE
|
||||
```
|
||||
|
||||
任务层执行:
|
||||
|
||||
```text
|
||||
emcTrajPause()
|
||||
interpResumeState = 当前解释器状态
|
||||
interpState = PAUSED
|
||||
task_paused = 1
|
||||
```
|
||||
|
||||
效果:
|
||||
|
||||
- 轨迹规划暂停。
|
||||
- 已进入 motion 队列的运动按 LinuxCNC pause 逻辑停止/保持。
|
||||
- AXIS 状态显示为 paused。
|
||||
- 后续恢复会回到 `interpResumeState`。
|
||||
|
||||
## 9. 恢复暂停
|
||||
|
||||
### 9.1 执行入口
|
||||
|
||||
AXIS 恢复按钮和 `s` 键执行:
|
||||
|
||||
```python
|
||||
def task_resume(*event):
|
||||
s.poll()
|
||||
if not s.paused:
|
||||
return
|
||||
if s.task_mode not in (linuxcnc.MODE_AUTO, linuxcnc.MODE_MDI):
|
||||
return
|
||||
ensure_mode(linuxcnc.MODE_AUTO, linuxcnc.MODE_MDI)
|
||||
c.auto(linuxcnc.AUTO_RESUME)
|
||||
```
|
||||
|
||||
AXIS 的暂停/恢复合并按钮 `task_pauseresume()` 还会检查:
|
||||
|
||||
```text
|
||||
resume-inhibit
|
||||
```
|
||||
|
||||
如果该 HAL pin 为真,则禁止恢复。本配置中 AXIS 创建了 `axisui.resume-inhibit` 输入 pin,但 `xyzbc-trt.ini` 没有把它接到其他信号,默认不阻塞恢复。
|
||||
|
||||
### 9.2 先决条件
|
||||
|
||||
- 当前必须已经暂停,即 `s.paused` 为真。
|
||||
- 当前模式必须是 `MODE_AUTO` 或 `MODE_MDI`。
|
||||
- 对合并暂停/恢复按钮,`resume-inhibit` 不能为真。
|
||||
|
||||
### 9.3 机床状态影响
|
||||
|
||||
恢复发送:
|
||||
|
||||
```text
|
||||
EMC_TASK_PLAN_RESUME
|
||||
```
|
||||
|
||||
任务层执行:
|
||||
|
||||
```text
|
||||
emcTrajResume()
|
||||
interpState = interpResumeState
|
||||
task_paused = 0
|
||||
motion.traj.single_stepping = 0
|
||||
stepping = 0
|
||||
steppingWait = 0
|
||||
```
|
||||
|
||||
效果:
|
||||
|
||||
- 轨迹恢复。
|
||||
- 解释器继续从暂停点运行。
|
||||
- 关闭单步状态,回到连续运行。
|
||||
|
||||
## 10. 单步执行
|
||||
|
||||
### 10.1 执行入口
|
||||
|
||||
AXIS 单步按钮和 `t` 键执行:
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
### 10.2 先决条件
|
||||
|
||||
常见使用方式:
|
||||
|
||||
- 机床上电。
|
||||
- 已回零。
|
||||
- 程序已打开。
|
||||
- 当前可切换到 `MODE_AUTO`。
|
||||
- 如果不是自动空闲状态,AXIS 会先清除高亮并执行 `run_warn()` 检查;如果用户取消或检查失败,则不单步。
|
||||
|
||||
任务层对第一次单步有特殊处理:
|
||||
|
||||
- 如果自动模式空闲时收到 `EMC_TASK_PLAN_STEP`,会先发起从第 0 行运行,再立即暂停轨迹,使程序进入单步逻辑。
|
||||
|
||||
### 10.3 机床状态影响
|
||||
|
||||
单步发送:
|
||||
|
||||
```text
|
||||
EMC_TASK_PLAN_STEP
|
||||
```
|
||||
|
||||
任务层按当前执行阶段处理:
|
||||
|
||||
- 在自动空闲初次单步时:
|
||||
- 创建 `EMC_TASK_PLAN_RUN(line=0)`
|
||||
- 执行 run
|
||||
- 调用 `emcTrajPause()`
|
||||
- 在解释器读取/等待/暂停过程中:
|
||||
- `motion.traj.single_stepping = 1`
|
||||
- `stepping = 1`
|
||||
- `steppingWait = 0`
|
||||
- 如果轨迹队列已有暂停运动,单步会推进队列中的下一步
|
||||
|
||||
效果:
|
||||
|
||||
- 每次点击只推进一个解释/运动步骤。
|
||||
- 与“恢复暂停”不同,单步不会切回连续运行。
|
||||
- 恢复暂停会清除 single stepping。
|
||||
|
||||
## 11. 当前 5 轴仿真配置的特殊注意点
|
||||
|
||||
### 11.1 启动默认不能直接运行
|
||||
|
||||
启动后通常处于急停或未上电状态。正确顺序是:
|
||||
|
||||
```text
|
||||
解除急停 -> 上电 -> Home All -> 执行程序
|
||||
```
|
||||
|
||||
如果未回零直接执行程序,任务层会报:
|
||||
|
||||
```text
|
||||
Can't run a program when not homed
|
||||
```
|
||||
|
||||
### 11.2 Home 与 switchkins 的关系
|
||||
|
||||
本配置 `sparm=identityfirst`,启动默认:
|
||||
|
||||
```text
|
||||
motion.switchkins-type = 0
|
||||
```
|
||||
|
||||
也就是 identity kinematics。回零应优先在 identity 下完成。演示程序内部也会反复使用 `M429` 回到 identity 后再做安全定位和坐标设置。
|
||||
|
||||
### 11.3 执行程序与 PyVCP 运动学按钮的关系
|
||||
|
||||
主运行按钮执行当前打开的 G-code。右侧 PyVCP 的:
|
||||
|
||||
- `IDENTITY`
|
||||
- `TCP:XYZBC`
|
||||
- `userk`
|
||||
|
||||
不是主运行控制按钮,它们通过 `switchkins_postgui.hal` 接到:
|
||||
|
||||
```text
|
||||
halui.mdi-command-00 -> M429
|
||||
halui.mdi-command-01 -> M428
|
||||
halui.mdi-command-02 -> M430
|
||||
```
|
||||
|
||||
这些按钮本质是发送 MDI 命令切换运动学,不负责运行、暂停或恢复程序。
|
||||
|
||||
### 11.4 仿真 HAL 对状态的响应
|
||||
|
||||
`basic_sim.tcl` 建立的关键闭环:
|
||||
|
||||
```text
|
||||
iocontrol.0.user-enable-out -> iocontrol.0.emc-enable-in
|
||||
motion.motion-enabled -> JN_mux.sel
|
||||
joint.N.amp-enable-out -> JN_pid.enable
|
||||
joint.N.motor-pos-cmd -> JN_pid.command
|
||||
JN_pid.output -> JN_mux.in1
|
||||
JN_mux.out -> joint.N.motor-pos-fb
|
||||
```
|
||||
|
||||
因此:
|
||||
|
||||
- 急停/下电会关闭 motion enable,反馈保持,不继续跟随命令。
|
||||
- 上电后 motion enable 打开,仿真伺服闭环重新允许命令传递到反馈。
|
||||
- Home 通过 `sim_home_switch` 给每个 `joint.N.home-sw-in` 提供模拟 home switch。
|
||||
|
||||
## 12. 推荐操作流程
|
||||
|
||||
启动:
|
||||
|
||||
```bash
|
||||
cd /home/mes123456/linuxcnc-master
|
||||
scripts/rip-environment linuxcnc configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini
|
||||
```
|
||||
|
||||
标准执行顺序:
|
||||
|
||||
```text
|
||||
1. 点击急停按钮或 F1:解除急停,使状态到 STATE_ESTOP_RESET。
|
||||
2. 点击上电按钮或 F2:进入 STATE_ON。
|
||||
3. 点击 Home All 或 Ctrl-Home:5 个关节全部回零。
|
||||
4. 确认已打开 demos/xyzbc_switchkins.ngc。
|
||||
5. 点击 Run 或按 r:进入 MODE_AUTO 并执行程序。
|
||||
6. 需要临时停止时按 Pause 或 p。
|
||||
7. 需要继续时按 Resume 或 s。
|
||||
8. 需要逐步观察时按 Step 或 t。
|
||||
9. 任何异常情况下按 F1 急停。
|
||||
```
|
||||
|
||||
## 13. 对应程序链路汇总
|
||||
|
||||
### 13.1 急停、解除急停、上电
|
||||
|
||||
```text
|
||||
AXIS 按钮/F1/F2
|
||||
-> axis.py estop_clicked() / onoff_clicked()
|
||||
-> linuxcnc.command().state(...)
|
||||
-> emcmodule.cc state()
|
||||
-> EMC_TASK_SET_STATE
|
||||
-> emctaskmain.cc emcTaskIssueCommand()
|
||||
-> emctask.cc emcTaskSetState()
|
||||
-> taskintf.cc emcTrajEnable/Disable, emcMotionAbort
|
||||
-> realtime motion / IO / HAL 状态变化
|
||||
```
|
||||
|
||||
### 13.2 Home
|
||||
|
||||
```text
|
||||
AXIS Home All / Home Joint / Ctrl-Home / Home
|
||||
-> axis.py home_all_joints() / home_joint()
|
||||
-> go_home()
|
||||
-> linuxcnc.command().home(...)
|
||||
-> emcmodule.cc home()
|
||||
-> EMC_JOINT_HOME
|
||||
-> emctaskmain.cc emcJointHome()
|
||||
-> taskintf.cc EMCMOT_JOINT_HOME
|
||||
-> realtime motion homing
|
||||
-> basic_sim.tcl simulated_home / sim_home_switch
|
||||
-> joint.N.homed = true
|
||||
```
|
||||
|
||||
### 13.3 运行、暂停、恢复、单步
|
||||
|
||||
```text
|
||||
AXIS Run/Pause/Resume/Step
|
||||
-> axis.py task_run/task_pause/task_resume/task_step
|
||||
-> linuxcnc.command().auto(...)
|
||||
-> emcmodule.cc emcauto()
|
||||
-> EMC_TASK_PLAN_RUN / PAUSE / RESUME / STEP
|
||||
-> emctaskmain.cc
|
||||
-> RS274NGC interpreter / task planner / trajectory planner
|
||||
-> realtime motion
|
||||
-> HAL 仿真闭环
|
||||
-> Vismach 与 AXIS 状态显示
|
||||
```
|
||||
|
||||
694
web-rtcp-5axis-xyzbc-trt-sim-plan/doc/AXIS暂停按钮LinuxCNC调用链分析.md
Normal file
694
web-rtcp-5axis-xyzbc-trt-sim-plan/doc/AXIS暂停按钮LinuxCNC调用链分析.md
Normal file
@@ -0,0 +1,694 @@
|
||||
# AXIS 主界面暂停按钮 LinuxCNC 调用链分析
|
||||
|
||||
## 目的
|
||||
|
||||
本文分析 `/home/mes123456/cnc_wams/linuxcnc` 源码中 AXIS 主界面“暂停按钮”的完整调用链,包括 UI、Python 扩展、NML task 命令、task 状态机、motion 命令和轨迹规划器 TP 的实现。目标是为 `/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan` 的暂停按钮修正提供对标依据。
|
||||
|
||||
结论先行:AXIS 的暂停不是只改一个 `runState`,而是同时维护两层暂停语义:
|
||||
|
||||
1. task/interpreter 暂停:`EMC_TASK_PLAN_PAUSE` 使 `task.interpState = PAUSED`,保存 `interpResumeState`,并设置 `task.task_paused = 1`。
|
||||
2. motion/trajectory 暂停:task 调用 `emcTrajPause()`,motion 收到 `EMCMOT_PAUSE` 后调用 `tpPause()`,轨迹规划器把 `tp->pausing = 1`,后续插补以速度目标 0 做受控减速,并通过 `motion.traj.paused` 对外反馈。
|
||||
|
||||
因此 Web 暂停按钮要同时冻结“解释器继续读行”和“运动采样继续推进”,并保留恢复前的解释器状态。
|
||||
|
||||
## 调用链总览
|
||||
|
||||
AXIS 工具栏暂停按钮链条:
|
||||
|
||||
```text
|
||||
share/axis/tcl/axis.tcl
|
||||
.toolbar.program_pause -command task_pauseresume
|
||||
-> src/emc/usr_intf/axis/scripts/axis.py
|
||||
commands.task_pauseresume()
|
||||
-> linuxcnc.command().auto(AUTO_PAUSE 或 AUTO_RESUME)
|
||||
-> src/emc/usr_intf/axis/extensions/emcmodule.cc
|
||||
emcauto()
|
||||
-> 发送 EMC_TASK_PLAN_PAUSE / EMC_TASK_PLAN_RESUME NML 命令
|
||||
-> src/emc/task/emctaskmain.cc
|
||||
emcTaskPlan() / emcTaskIssueCommand()
|
||||
-> pause: emcTrajPause(); interpState=PAUSED; task_paused=1
|
||||
-> resume: emcTrajResume(); interpState=interpResumeState; task_paused=0
|
||||
-> src/emc/task/taskintf.cc
|
||||
emcTrajPause() / emcTrajResume()
|
||||
-> 写 EMCMOT_PAUSE / EMCMOT_RESUME 到 motion
|
||||
-> src/emc/motion/command.c
|
||||
EMCMOT_PAUSE: tpPause(); emcmotStatus->paused=1
|
||||
EMCMOT_RESUME: tpResume(); emcmotStatus->paused=0
|
||||
-> src/emc/tp/tp.c
|
||||
tpPause(): tp->pausing=1
|
||||
tpResume(): tp->pausing=0
|
||||
```
|
||||
|
||||
菜单 Pause 和 Resume 是分开的命令;工具栏按钮是同一个 `task_pauseresume`,会根据当前状态决定暂停还是恢复。
|
||||
|
||||
## AXIS UI 层
|
||||
|
||||
### Tcl 工具栏按钮
|
||||
|
||||
`linuxcnc/share/axis/tcl/axis.tcl:543-549` 定义工具栏暂停按钮:
|
||||
|
||||
```tcl
|
||||
Button .toolbar.program_pause \
|
||||
-command task_pauseresume \
|
||||
-helptext [_ "Pause \[P\] / resume \[S\] execution"] \
|
||||
-image [load_image tool_pause]
|
||||
```
|
||||
|
||||
这个按钮不是固定 Pause,也不是固定 Resume,而是 toggle 行为:运行时点击发送 pause,已暂停时点击发送 resume。
|
||||
|
||||
同一文件 `axis.tcl:131-139` 定义菜单项:
|
||||
|
||||
```tcl
|
||||
.menu.machine add command -accelerator P -command task_pause
|
||||
.menu.machine add command -accelerator S -command task_resume
|
||||
```
|
||||
|
||||
也就是说:
|
||||
|
||||
- 菜单 Pause 调 `task_pause`
|
||||
- 菜单 Resume 调 `task_resume`
|
||||
- 工具栏 Pause 图标调 `task_pauseresume`
|
||||
|
||||
Web 项目当前工具栏 `tbtn_pause` 对标的是 `task_pauseresume`,菜单 Pause/Resume 才应分别对标 `task_pause` 和 `task_resume`。
|
||||
|
||||
### AXIS Python 命令入口
|
||||
|
||||
`linuxcnc/src/emc/usr_intf/axis/scripts/axis.py:2402-2407`:
|
||||
|
||||
```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)
|
||||
```
|
||||
|
||||
菜单 Pause 的门槛很严格:
|
||||
|
||||
- 必须是 `MODE_AUTO`
|
||||
- `interp_state` 必须是 `INTERP_READING` 或 `INTERP_WAITING`
|
||||
- 满足后发送 `AUTO_PAUSE`
|
||||
|
||||
`axis.py:2424-2431`:
|
||||
|
||||
```python
|
||||
def task_resume(*event):
|
||||
s.poll()
|
||||
if not s.paused:
|
||||
return
|
||||
if s.task_mode not in (linuxcnc.MODE_AUTO, linuxcnc.MODE_MDI):
|
||||
return
|
||||
ensure_mode(linuxcnc.MODE_AUTO, linuxcnc.MODE_MDI)
|
||||
c.auto(linuxcnc.AUTO_RESUME)
|
||||
```
|
||||
|
||||
菜单 Resume 的关键判断不是 `interp_state == PAUSED`,而是 `s.paused`。在 AXIS Python stat 中,`s.paused` 对应 motion/traj 层的 paused 状态,而 `s.task_paused` 是 task 层状态。两者都要关注。
|
||||
|
||||
`axis.py:2433-2443`:
|
||||
|
||||
```python
|
||||
def task_pauseresume(*event):
|
||||
if s.task_mode not in (linuxcnc.MODE_AUTO, linuxcnc.MODE_MDI):
|
||||
return
|
||||
ensure_mode(linuxcnc.MODE_AUTO, linuxcnc.MODE_MDI)
|
||||
s.poll()
|
||||
if s.paused:
|
||||
if resume_inhibit: return
|
||||
c.auto(linuxcnc.AUTO_RESUME)
|
||||
elif s.interp_state != linuxcnc.INTERP_IDLE:
|
||||
c.auto(linuxcnc.AUTO_PAUSE)
|
||||
```
|
||||
|
||||
工具栏 toggle 的真实语义:
|
||||
|
||||
- 如果 motion/traj 已 paused:发送 `AUTO_RESUME`
|
||||
- 否则只要 interpreter 不是 idle:发送 `AUTO_PAUSE`
|
||||
- 如果 interpreter idle:点击无效
|
||||
- 允许 `MODE_AUTO` 或 `MODE_MDI`
|
||||
- 恢复还会受 `resume_inhibit` 影响
|
||||
|
||||
`axis.py:906-910` 周期性把 LinuxCNC stat 写入 Tcl 变量:
|
||||
|
||||
- `vars.task_paused <- self.stat.task_paused`
|
||||
- `vars.interp_pause <- self.stat.paused`
|
||||
|
||||
这说明 AXIS UI 同时观察 task 暂停和 motion 暂停。
|
||||
|
||||
键盘绑定在 `axis.py:3163-3165`:
|
||||
|
||||
- `s` -> `commands.task_resume`
|
||||
- `p` -> `commands.task_pause`
|
||||
|
||||
## Python 扩展和 NML 命令
|
||||
|
||||
AXIS 的 `linuxcnc.command().auto()` 在 C++ Python 扩展中实现。
|
||||
|
||||
`linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:68-73` 定义本地 AUTO 常量:
|
||||
|
||||
```cpp
|
||||
#define LOCAL_AUTO_RUN (0)
|
||||
#define LOCAL_AUTO_PAUSE (1)
|
||||
#define LOCAL_AUTO_RESUME (2)
|
||||
#define LOCAL_AUTO_STEP (3)
|
||||
```
|
||||
|
||||
`emcmodule.cc:2093-2129` 的 `emcauto()` 根据参数创建并发送 NML 命令:
|
||||
|
||||
- `LOCAL_AUTO_PAUSE` -> `EMC_TASK_PLAN_PAUSE`
|
||||
- `LOCAL_AUTO_RESUME` -> `EMC_TASK_PLAN_RESUME`
|
||||
- `LOCAL_AUTO_STEP` -> `EMC_TASK_PLAN_STEP`
|
||||
|
||||
关键片段:
|
||||
|
||||
```cpp
|
||||
case LOCAL_AUTO_PAUSE:
|
||||
emcSendCommand(s, pause);
|
||||
break;
|
||||
case LOCAL_AUTO_RESUME:
|
||||
emcSendCommand(s, resume);
|
||||
break;
|
||||
case LOCAL_AUTO_STEP:
|
||||
emcSendCommand(s, step);
|
||||
break;
|
||||
```
|
||||
|
||||
NML 类型定义在 `linuxcnc/src/emc/nml_intf/emc.hh:127-135`:
|
||||
|
||||
- `EMC_TASK_PLAN_PAUSE_TYPE = 510`
|
||||
- `EMC_TASK_PLAN_STEP_TYPE = 511`
|
||||
- `EMC_TASK_PLAN_RESUME_TYPE = 512`
|
||||
|
||||
NML 消息类定义在 `linuxcnc/src/emc/nml_intf/emc_nml.hh:1276-1327`。这三个消息类没有额外字段,核心信息就是消息类型本身。
|
||||
|
||||
## LinuxCNC 状态字段
|
||||
|
||||
解释器状态定义在 `linuxcnc/src/emc/nml_intf/emc.hh:220-226`:
|
||||
|
||||
```cpp
|
||||
enum class EMC_TASK_INTERP {
|
||||
IDLE = 1,
|
||||
READING = 2,
|
||||
PAUSED = 3,
|
||||
WAITING = 4
|
||||
};
|
||||
```
|
||||
|
||||
motion/traj 状态字段在 `linuxcnc/src/emc/nml_intf/emc_nml.hh:965-991`:
|
||||
|
||||
- `EMC_TRAJ_STAT::paused`
|
||||
- `EMC_TRAJ_STAT::single_stepping`
|
||||
- `EMC_TRAJ_STAT::queue`
|
||||
- `EMC_TRAJ_STAT::activeQueue`
|
||||
- `EMC_TRAJ_STAT::id`
|
||||
|
||||
task 状态字段在 `linuxcnc/src/emc/nml_intf/emc_nml.hh:1448-1471`:
|
||||
|
||||
- `EMC_TASK_STAT::interpState`
|
||||
- `EMC_TASK_STAT::task_paused`
|
||||
|
||||
AXIS Python 扩展把这些字段暴露给 Python:
|
||||
|
||||
- `emcmodule.cc:1137` 暴露 `interp_state`
|
||||
- `emcmodule.cc:1148` 暴露 `task_paused`
|
||||
- `emcmodule.cc:3380-3383` 暴露 `INTERP_IDLE/READING/PAUSED/WAITING`
|
||||
|
||||
## task 层实现
|
||||
|
||||
`linuxcnc/src/emc/task/emctaskmain.cc:15-35` 说明 task 主循环原则:
|
||||
|
||||
1. 周期性调用 `emcTaskPlan()` 和 `emcTaskExecute()`。
|
||||
2. `emcTaskPlan()` 读取新命令,并按机器模式和状态决定处理方式。
|
||||
3. AUTO 模式下解释器会把命令追加到 `interp_list`。
|
||||
4. `emcTaskExecute()` 根据 precondition/postcondition 从 `interp_list` 取命令执行。
|
||||
5. immediate command 不走 interp list pre/postcondition。
|
||||
|
||||
这解释了一个关键现象:暂停可能作为 GUI 即时命令被处理,也可能作为解释器列表中的 pause/optional stop 被排队处理。
|
||||
|
||||
### 保存恢复状态
|
||||
|
||||
`emctaskmain.cc:427`:
|
||||
|
||||
```cpp
|
||||
static EMC_TASK_INTERP interpResumeState = EMC_TASK_INTERP::IDLE;
|
||||
```
|
||||
|
||||
暂停时如果当前不是 PAUSED,就保存当前解释器状态;恢复时还原:
|
||||
|
||||
- 暂停保存:`emctaskmain.cc:2337-2343`
|
||||
- 恢复还原:`emctaskmain.cc:2369-2375`
|
||||
|
||||
### PLAN_PAUSE 执行点
|
||||
|
||||
`emctaskmain.cc:2337-2345`:
|
||||
|
||||
```cpp
|
||||
case EMC_TASK_PLAN_PAUSE_TYPE:
|
||||
emcTrajPause();
|
||||
if (emcStatus->task.interpState != EMC_TASK_INTERP::PAUSED) {
|
||||
interpResumeState = emcStatus->task.interpState;
|
||||
}
|
||||
emcStatus->task.interpState = EMC_TASK_INTERP::PAUSED;
|
||||
emcStatus->task.task_paused = 1;
|
||||
retval = 0;
|
||||
break;
|
||||
```
|
||||
|
||||
这就是 task 层暂停的核心:
|
||||
|
||||
- 先暂停轨迹:`emcTrajPause()`
|
||||
- 保存恢复前解释器状态:`interpResumeState`
|
||||
- 设置解释器状态为 `PAUSED`
|
||||
- 设置 `task_paused = 1`
|
||||
|
||||
### PLAN_RESUME 执行点
|
||||
|
||||
`emctaskmain.cc:2369-2377`:
|
||||
|
||||
```cpp
|
||||
case EMC_TASK_PLAN_RESUME_TYPE:
|
||||
emcTrajResume();
|
||||
emcStatus->task.interpState = interpResumeState;
|
||||
emcStatus->task.task_paused = 0;
|
||||
emcStatus->motion.traj.single_stepping = 0;
|
||||
stepping = 0;
|
||||
steppingWait = 0;
|
||||
retval = 0;
|
||||
break;
|
||||
```
|
||||
|
||||
恢复时:
|
||||
|
||||
- 先恢复轨迹:`emcTrajResume()`
|
||||
- 把解释器状态恢复到暂停前的 `interpResumeState`
|
||||
- 清 `task_paused`
|
||||
- 清单步状态
|
||||
|
||||
### PAUSED 状态下不继续取解释器队列
|
||||
|
||||
`emctaskmain.cc:2614-2624` 在 `EMC_TASK_EXEC::DONE` 分支中只有当 `interpState != PAUSED` 时才继续从 `interp_list` 取下一条命令:
|
||||
|
||||
```cpp
|
||||
if (!emcStatus->motion.traj.queueFull &&
|
||||
emcStatus->task.interpState != EMC_TASK_INTERP::PAUSED) {
|
||||
emcTaskCommand = interp_list.get();
|
||||
}
|
||||
```
|
||||
|
||||
这对 Web 很重要:暂停时不仅速度为 0,还必须停止继续推进解释器命令和 UI sample。否则会出现“按钮显示暂停,但 G-code 行号、sampleIndex 或刀具位置继续走”的问题。
|
||||
|
||||
### precondition 中的排队暂停
|
||||
|
||||
`emctaskmain.cc:1569-1572`:
|
||||
|
||||
```cpp
|
||||
case EMC_TASK_PLAN_PAUSE_TYPE:
|
||||
case EMC_TASK_PLAN_OPTIONAL_STOP_TYPE:
|
||||
return EMC_TASK_EXEC::WAITING_FOR_MOTION_AND_IO;
|
||||
```
|
||||
|
||||
如果 pause 命令来自解释器列表,它会等前面的 motion 和 IO 完成后再执行。这不同于用户点击 GUI 时的 immediate pause。Web 如果模拟 M0/M1 或解释器排队暂停,应等当前 motion/IO 条件满足;如果模拟 AXIS 工具栏点击,则应立即向 task/motion 发暂停请求。
|
||||
|
||||
### STEP 与暂停的关系
|
||||
|
||||
`emctaskmain.cc:1254-1265`:在 PAUSED 状态下收到 STEP,如果 motion queue 里有暂停的 motion,就调用 `emcTrajStep()`,否则恢复 interpreter 到 `interpResumeState`。
|
||||
|
||||
`emctaskmain.cc:2538-2549` 的 `STEPPING_CHECK()` 用 `stepping/steppingWait/steppedLine` 控制只执行一步。
|
||||
|
||||
这说明 STEP 不是普通 resume,也不是永远推进一个 UI sample;它是“在 paused 状态下短暂运行,直到 motion id/line 改变后再暂停”。
|
||||
|
||||
## task 到 motion 的接口
|
||||
|
||||
`linuxcnc/src/emc/task/taskintf.cc:1417-1422`:
|
||||
|
||||
```cpp
|
||||
int emcTrajPause()
|
||||
{
|
||||
emcmotCommand.command = EMCMOT_PAUSE;
|
||||
return usrmotWriteEmcmotCommand(&emcmotCommand);
|
||||
}
|
||||
```
|
||||
|
||||
`taskintf.cc:1438-1449`:
|
||||
|
||||
```cpp
|
||||
int emcTrajStep()
|
||||
{
|
||||
emcmotCommand.command = EMCMOT_STEP;
|
||||
return usrmotWriteEmcmotCommand(&emcmotCommand);
|
||||
}
|
||||
|
||||
int emcTrajResume()
|
||||
{
|
||||
emcmotCommand.command = EMCMOT_RESUME;
|
||||
return usrmotWriteEmcmotCommand(&emcmotCommand);
|
||||
}
|
||||
```
|
||||
|
||||
task 层不自己停止电机轨迹,它把 `EMCMOT_PAUSE/RESUME/STEP` 写给 motion。
|
||||
|
||||
motion 状态回读在 `taskintf.cc:1678`:
|
||||
|
||||
```cpp
|
||||
stat->paused = emcmotStatus.paused;
|
||||
```
|
||||
|
||||
这就是 AXIS 的 `s.paused` 来源。
|
||||
|
||||
## motion 层实现
|
||||
|
||||
motion 命令枚举在 `linuxcnc/src/emc/motion/motion.h:94-104`:
|
||||
|
||||
- `EMCMOT_PAUSE`
|
||||
- `EMCMOT_REVERSE`
|
||||
- `EMCMOT_FORWARD`
|
||||
- `EMCMOT_RESUME`
|
||||
- `EMCMOT_STEP`
|
||||
|
||||
motion 状态字段 `emcmotStatus->paused` 在 `motion.h:631-640` 定义,初始化在 `motion.c:940-951` 清零。
|
||||
|
||||
`linuxcnc/src/emc/motion/command.c:1234-1240`:
|
||||
|
||||
```c
|
||||
case EMCMOT_PAUSE:
|
||||
tpPause(&emcmotInternal->coord_tp);
|
||||
emcmotStatus->paused = 1;
|
||||
break;
|
||||
```
|
||||
|
||||
`command.c:1256-1263`:
|
||||
|
||||
```c
|
||||
case EMCMOT_RESUME:
|
||||
emcmotStatus->stepping = 0;
|
||||
tpResume(&emcmotInternal->coord_tp);
|
||||
emcmotStatus->paused = 0;
|
||||
break;
|
||||
```
|
||||
|
||||
`command.c:1265-1274`:
|
||||
|
||||
```c
|
||||
case EMCMOT_STEP:
|
||||
if(emcmotStatus->paused) {
|
||||
emcmotInternal->idForStep = emcmotStatus->id;
|
||||
emcmotStatus->stepping = 1;
|
||||
tpResume(&emcmotInternal->coord_tp);
|
||||
emcmotStatus->paused = 1;
|
||||
}
|
||||
```
|
||||
|
||||
STEP 的 motion 语义是:在 paused 时记录当前 motion id,短暂 resume TP,但状态仍视为 paused,等 id 改变后自动再 pause。
|
||||
|
||||
`linuxcnc/src/emc/motion/control.c:2211-2218`:
|
||||
|
||||
```c
|
||||
if (emcmotStatus->stepping && emcmotInternal->idForStep != emcmotStatus->id) {
|
||||
tpPause(&emcmotInternal->coord_tp);
|
||||
emcmotStatus->stepping = 0;
|
||||
emcmotStatus->paused = 1;
|
||||
}
|
||||
```
|
||||
|
||||
这就是 STEP 自动回到暂停的实现。
|
||||
|
||||
## 轨迹规划器 TP 实现
|
||||
|
||||
TP 结构字段在 `linuxcnc/src/emc/tp/tp_types.h:117-126`:
|
||||
|
||||
```c
|
||||
int nextId;
|
||||
int execId;
|
||||
int done;
|
||||
int depth;
|
||||
int activeDepth;
|
||||
int aborting;
|
||||
int pausing;
|
||||
int reverse_run;
|
||||
```
|
||||
|
||||
TP API 在 `linuxcnc/src/emc/tp/tp.h:45-48`:
|
||||
|
||||
```c
|
||||
int tpRunCycle(TP_STRUCT * tp, long period);
|
||||
int tpPause(TP_STRUCT * tp);
|
||||
int tpResume(TP_STRUCT * tp);
|
||||
int tpAbort(TP_STRUCT * tp);
|
||||
```
|
||||
|
||||
`linuxcnc/src/emc/tp/tp.c:4225-4240`:
|
||||
|
||||
```c
|
||||
int tpPause(TP_STRUCT * const tp)
|
||||
{
|
||||
if (0 == tp) return TP_ERR_FAIL;
|
||||
tp->pausing = 1;
|
||||
return TP_ERR_OK;
|
||||
}
|
||||
|
||||
int tpResume(TP_STRUCT * const tp)
|
||||
{
|
||||
if (0 == tp) return TP_ERR_FAIL;
|
||||
tp->pausing = 0;
|
||||
return TP_ERR_OK;
|
||||
}
|
||||
```
|
||||
|
||||
暂停本质是设置 `tp->pausing`。真正的减速逻辑在规划循环里体现。
|
||||
|
||||
`tp.c:247-258`:
|
||||
|
||||
```c
|
||||
bool pausing = tp->pausing && (tc->synchronized == TC_SYNC_NONE || tc->synchronized == TC_SYNC_VELOCITY);
|
||||
if (pausing) {
|
||||
return 0.0;
|
||||
}
|
||||
```
|
||||
|
||||
暂停时 feed scale 返回 0,使目标速度降到 0。
|
||||
|
||||
`tp.c:2832-2838`:
|
||||
|
||||
```c
|
||||
bool is_pausing = tp->pausing && (...);
|
||||
bool use_velocity_control = (is_pausing || is_aborting || emcmotStatus->net_feed_scale <= TP_VEL_EPSILON);
|
||||
```
|
||||
|
||||
暂停/abort/feed override 为 0 都进入 velocity-control 风格的减速规划。
|
||||
|
||||
`tp.c:4132-4135` 注释说明如果 aborting 或 pausing 且速度到 0,就不需要继续规划并返回 stopped。
|
||||
|
||||
所以 LinuxCNC 的 motion pause 不是把位置瞬间锁死,也不是清空队列,而是让当前规划器以受控方式减速到 0;到达 paused 稳态后,`current_vel` 为 0,队列和当前 id 保留,恢复时从原队列继续。
|
||||
|
||||
## Web 当前实现的对照问题
|
||||
|
||||
从当前 Web 项目看,相关文件主要是:
|
||||
|
||||
- `app/src/ui/axis-shell.js`
|
||||
- `app/src/state/linuxcnc-task-policy.js`
|
||||
- `app/src/state/store.js`
|
||||
|
||||
Web 工具栏暂停按钮在 `axis-shell.js:170-182` 渲染,`tbtn_pause` 绑定 action `pause-resume`。`axis-shell.js:736-748` 将其分发为 `PAUSE_RESUME`,这与 AXIS 工具栏对标方向正确。
|
||||
|
||||
Web 当前 `isProgramPaused()` 在 `axis-shell.js:616-620` 使用:
|
||||
|
||||
```js
|
||||
state.runState === "paused" ||
|
||||
state.machine?.interpState === "paused" ||
|
||||
state.machine?.taskPaused === true
|
||||
```
|
||||
|
||||
这少了一个 LinuxCNC 真实关键字段:motion/traj paused,也就是 AXIS 的 `s.paused`。Web 如果只看 `taskPaused` 或 `interpState`,可能无法准确模拟“运动层已经 paused,但 task 状态尚未完全同步”的短窗口。
|
||||
|
||||
Web policy 在 `linuxcnc-task-policy.js:161-188` 对 PAUSE/RESUME 的 gating 大体接近 AXIS:
|
||||
|
||||
- 菜单 PAUSE 要求 task on、auto、interp reading/waiting。
|
||||
- pause-resume 允许 auto/mdi,interp idle 时忽略。
|
||||
- RESUME 要求 interp paused。
|
||||
|
||||
但 AXIS `task_resume()` 和 `task_pauseresume()` 的恢复判断用的是 `s.paused`,即 motion/traj paused。Web 当前恢复更多依赖 `interpState === "paused"`。建议增加或统一 `machine.motionPaused` / `programRuntimeFeedback.paused` 字段,并将 resume gate 改为:
|
||||
|
||||
```text
|
||||
canResume = taskState on
|
||||
&& taskMode in auto/mdi
|
||||
&& (motionPaused || interpState paused || taskPaused)
|
||||
&& !resumeInhibit
|
||||
```
|
||||
|
||||
Web `store.js:1760-1807` 的 PAUSE 分支已经设置:
|
||||
|
||||
- `interpState: "paused"`
|
||||
- `taskPaused: true`
|
||||
- `runState: "paused"`
|
||||
- `feed.currentVelocity: 0`
|
||||
- `programRuntimeFeedback` velocity 归零
|
||||
|
||||
但要严格对标 LinuxCNC,还需要确保暂停后不再推进:
|
||||
|
||||
- `programExecutionSampleIndex`
|
||||
- `programUiExecution.sampleIndex`
|
||||
- `programRuntimeFeedback.sampleIndex`
|
||||
- `activeLine/currentLine`
|
||||
- `axisPose`
|
||||
- `DRO`
|
||||
- `toolhead/toolAxis`
|
||||
- task/HAL status loop 的采样推进
|
||||
|
||||
LinuxCNC 的关键保护是 `emctaskmain.cc:2614-2624`:`interpState == PAUSED` 时不从 `interp_list` 取下一条。Web 也需要同等约束:所有周期性 playback/status loop 在 paused 时必须只 poll 状态,不推进 sample 或解释器队列。
|
||||
|
||||
## Web 修正建议
|
||||
|
||||
### 1. 拆清三种状态
|
||||
|
||||
建议 Web 状态显式拆成:
|
||||
|
||||
```js
|
||||
machine: {
|
||||
interpState: "idle" | "reading" | "paused" | "waiting",
|
||||
interpResumeState: "idle" | "reading" | "waiting",
|
||||
taskPaused: boolean,
|
||||
motionPaused: boolean,
|
||||
resumeInhibit: boolean
|
||||
}
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `taskPaused` 对标 `EMC_TASK_STAT::task_paused`
|
||||
- `motionPaused` 对标 `EMC_TRAJ_STAT::paused` / AXIS `s.paused`
|
||||
- `interpState` 对标 `EMC_TASK_STAT::interpState`
|
||||
- `interpResumeState` 对标 task 层静态变量 `interpResumeState`
|
||||
|
||||
### 2. 工具栏 toggle 必须按 AXIS 判断
|
||||
|
||||
对标 `axis.py:2433-2443`:
|
||||
|
||||
```js
|
||||
if (taskMode not in auto/mdi) return;
|
||||
if (motionPaused || taskPaused || interpState === "paused") {
|
||||
if (resumeInhibit) return;
|
||||
dispatch RESUME;
|
||||
} else if (interpState !== "idle") {
|
||||
dispatch PAUSE;
|
||||
}
|
||||
```
|
||||
|
||||
其中恢复优先看 `motionPaused`,不是只看 `interpState`。
|
||||
|
||||
### 3. PAUSE 动作必须先冻结 motion,再冻结 interpreter
|
||||
|
||||
对标 `emctaskmain.cc:2337-2343`:
|
||||
|
||||
```js
|
||||
motionPaused = true;
|
||||
if (interpState !== "paused") interpResumeState = interpState;
|
||||
interpState = "paused";
|
||||
taskPaused = true;
|
||||
runState = "paused";
|
||||
```
|
||||
|
||||
同时不应修改当前 `sampleIndex`、当前行、DRO 或 toolhead 位置,只把速度反馈归零。
|
||||
|
||||
### 4. RESUME 动作必须恢复到 interpResumeState
|
||||
|
||||
对标 `emctaskmain.cc:2369-2375`:
|
||||
|
||||
```js
|
||||
motionPaused = false;
|
||||
interpState = interpResumeState || "reading";
|
||||
taskPaused = false;
|
||||
singleStepping = false;
|
||||
runState = interpState === "reading" ? "running" : "idle";
|
||||
```
|
||||
|
||||
如果 `interpResumeState` 是 `waiting`,恢复后应回到 waiting,再由执行循环完成后转 idle,不要一律改成 reading。
|
||||
|
||||
### 5. STEP 不是普通 sample+1
|
||||
|
||||
对标 `command.c:1265-1274` 和 `control.c:2211-2218`:
|
||||
|
||||
Web STEP 应记录当前 motion id/sample id,短暂允许推进到下一个 motion id 或下一条解释器线,然后重新置:
|
||||
|
||||
- `interpState = "paused"`
|
||||
- `taskPaused = true`
|
||||
- `motionPaused = true`
|
||||
- `runState = "paused"` 或短暂 `"stepping"` 后回 `"paused"`
|
||||
|
||||
当前 Web 的 `STEP` 分支会推进 sample,这个方向可以保留,但结束状态必须回到 paused,并且不应变成长期 running。
|
||||
|
||||
### 6. 暂停时 status loop 只能刷新状态,不可推进执行
|
||||
|
||||
Web 的 task/HAL status loop 和 fallback playback 都要加硬条件:
|
||||
|
||||
```js
|
||||
if (state.machine.interpState === "paused" ||
|
||||
state.machine.taskPaused ||
|
||||
state.machine.motionPaused ||
|
||||
state.runState === "paused") {
|
||||
return freezeCurrentExecutionPatch(state);
|
||||
}
|
||||
```
|
||||
|
||||
冻结 patch 应保留当前 sample/pose/DRO/toolhead,只允许:
|
||||
|
||||
- velocity -> 0
|
||||
- paused flags -> true
|
||||
- operator message/status timestamp 更新
|
||||
|
||||
## 验收断言建议
|
||||
|
||||
暂停按钮要通过以下断言才算对标 LinuxCNC:
|
||||
|
||||
1. 工具栏点击运行中程序后:
|
||||
- `runState === "paused"`
|
||||
- `machine.interpState === "paused"`
|
||||
- `machine.taskPaused === true`
|
||||
- `machine.motionPaused === true`
|
||||
- `feed.currentVelocity === 0`
|
||||
2. 暂停后保持 5 秒:
|
||||
- `sampleIndex` 不变
|
||||
- `activeLine/currentLine` 不变
|
||||
- `axisPose` 不变
|
||||
- DRO 不变
|
||||
- canvas toolhead/tool axis 不变
|
||||
- runtime TCP 不变
|
||||
3. 再点工具栏暂停按钮:
|
||||
- 若 `resumeInhibit` false,则恢复到 `interpResumeState`
|
||||
- `motionPaused === false`
|
||||
- `taskPaused === false`
|
||||
4. 菜单 Pause 在 `interpState === "idle"` 时必须无效。
|
||||
5. 菜单 Resume 在 `motionPaused/taskPaused/interpState paused` 之外必须无效。
|
||||
6. STEP 后必须重新进入 paused 稳态,不允许持续 running。
|
||||
|
||||
## 最小调用链索引
|
||||
|
||||
| 层级 | 文件 | 关键行 | 作用 |
|
||||
| --- | --- | --- | --- |
|
||||
| Tcl UI | `linuxcnc/share/axis/tcl/axis.tcl` | 543-549 | 工具栏 pause/resume 按钮绑定 `task_pauseresume` |
|
||||
| 菜单 UI | `linuxcnc/share/axis/tcl/axis.tcl` | 131-139 | 菜单 Pause/Resume 分别绑定 `task_pause`/`task_resume` |
|
||||
| AXIS Python | `linuxcnc/src/emc/usr_intf/axis/scripts/axis.py` | 2402-2443 | 判断状态并调用 `c.auto(AUTO_PAUSE/RESUME)` |
|
||||
| Python stat | `axis.py` | 906-910 | 更新 `task_paused` 和 `interp_pause` UI 变量 |
|
||||
| Python 扩展 | `linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc` | 2093-2129 | `AUTO_PAUSE/RESUME/STEP` 转 NML 命令 |
|
||||
| NML 类型 | `linuxcnc/src/emc/nml_intf/emc.hh` | 127-135 | 定义 `EMC_TASK_PLAN_PAUSE/RESUME/STEP_TYPE` |
|
||||
| NML 类 | `linuxcnc/src/emc/nml_intf/emc_nml.hh` | 1276-1327 | 定义 task pause/resume/step 消息类 |
|
||||
| task 状态 | `linuxcnc/src/emc/nml_intf/emc.hh` | 220-226 | 定义 `IDLE/READING/PAUSED/WAITING` |
|
||||
| task 主循环 | `linuxcnc/src/emc/task/emctaskmain.cc` | 15-35 | 说明 plan/execute、interp_list、immediate command 架构 |
|
||||
| task pause | `linuxcnc/src/emc/task/emctaskmain.cc` | 2337-2345 | 调 `emcTrajPause`,设置 `interpState/task_paused` |
|
||||
| task resume | `linuxcnc/src/emc/task/emctaskmain.cc` | 2369-2377 | 调 `emcTrajResume`,恢复 `interpResumeState` |
|
||||
| task execute freeze | `linuxcnc/src/emc/task/emctaskmain.cc` | 2614-2624 | PAUSED 时不继续取 `interp_list` |
|
||||
| task->motion | `linuxcnc/src/emc/task/taskintf.cc` | 1417-1449 | 发送 `EMCMOT_PAUSE/STEP/RESUME` |
|
||||
| motion command | `linuxcnc/src/emc/motion/command.c` | 1234-1274 | 调 `tpPause/tpResume`,维护 `emcmotStatus->paused` |
|
||||
| motion step | `linuxcnc/src/emc/motion/control.c` | 2211-2218 | STEP 运动 id 改变后自动再 pause |
|
||||
| TP API | `linuxcnc/src/emc/tp/tp.c` | 4225-4240 | `tpPause/tpResume` 设置 `tp->pausing` |
|
||||
| TP 减速 | `linuxcnc/src/emc/tp/tp.c` | 247-258, 2832-2838 | pausing 时 feed scale/velocity control 使速度目标为 0 |
|
||||
|
||||
## 最终判断
|
||||
|
||||
LinuxCNC AXIS 暂停按钮可靠的根本原因是:UI 只发命令,task 层负责解释器状态和队列冻结,motion 层负责轨迹暂停,TP 层负责受控减速到 0。Web 项目的暂停如果“一直不好使用”,通常不是按钮绑定本身的问题,而是没有完整复现这四层状态同步:
|
||||
|
||||
- 没有 `motionPaused` 等价字段或没有用它做恢复判断;
|
||||
- 暂停后 status/playback loop 仍推进 sample;
|
||||
- `interpResumeState` 没有严格保留和恢复;
|
||||
- STEP 被实现成普通推进,而不是短暂恢复后再暂停;
|
||||
- 菜单 Pause、菜单 Resume、工具栏 Pause/Resume toggle 的门槛混在一起。
|
||||
|
||||
修正 Web 时应优先把暂停状态机改成 LinuxCNC 的双层模型:`task_paused/interpState` 管解释器,`motionPaused/currentVelocity` 管运动,执行循环在 paused 时冻结取样与队列推进。
|
||||
Reference in New Issue
Block a user