30 KiB
AXIS 主控制按钮调用链与 WASM 完善指南
生成时间:2026-07-07
分析范围:
- LinuxCNC 源码:
/home/mes123456/cnc_wams/linuxcnc - WASM 迁移工程:
/home/mes123456/cnc_wams/wasm-port - Web 5 轴仿真工程:
/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan
本文重点分析 AXIS 主界面的“急停、上电、Home、执行、暂停、单步执行”的调用链、状态机制、按钮前置条件,以及对 WASM/Web 项目的完善建议。
1. 总体架构结论
LinuxCNC 中 AXIS 按钮不是直接操作运动控制,而是走统一命令/状态通道:
AXIS Python/Tk 按钮
-> linuxcnc.command() Python C 扩展
-> NML 命令对象 EMC_TASK_*/EMC_JOINT_*
-> emcCommand NML 通道
-> task 主循环 emcTaskPlan()/emcTaskIssueCommand()
-> taskintf.cc 转为 EMCMOT_* motion 命令
-> motion 实时侧 command.c/control.c/homing.c
-> motion/status + task/status 合成为 emcStatus
-> linuxcnc.stat().poll() 供 AXIS 刷新 UI
核心状态不是由按钮自行保存,而是由 emcStatus 聚合:
emcStatus->task.state:任务状态,GUI 显示为STATE_ESTOP、STATE_ESTOP_RESET、STATE_ON等。emcStatus->task.mode:任务模式,MODE_MANUAL、MODE_AUTO、MODE_MDI。emcStatus->task.interpState:解释器状态,INTERP_IDLE、INTERP_READING、INTERP_PAUSED、INTERP_WAITING。emcStatus->task.task_paused:task 层暂停标志。emcStatus->motion.traj.paused:motion 轨迹规划器暂停标志。emcStatus->motion.traj.single_stepping:task/GUI 可见的单步执行标志。emcStatus->motion.joint[n].homed:每个 joint 的回零状态。emcStatus->motion.traj.enabled:motion 是否上使能,用于推导 task 是否STATE_ON。
需要特别注意:LinuxCNC 的 task 层主要是 C++,motion 层主要是 C。用户所说的 C++ 实现,在源码中实际跨越 emctask*.cc、taskintf.cc 和 motion 的 command.c、homing.c。
2. AXIS UI 按钮入口
AXIS 主界面位于:
linuxcnc/bin/axis
关键辅助函数:
def manual_ok(do_poll=True):
if do_poll: s.poll()
if s.task_state != linuxcnc.STATE_ON: return False
return s.interp_state == linuxcnc.INTERP_IDLE or (
s.task_mode == linuxcnc.MODE_MDI and
s.queued_mdi_commands < vars.max_queued_mdi_commands.get()
)
def ensure_mode(m, *p):
s.poll()
if s.task_mode == m or s.task_mode in p: return True
c.mode(m)
c.wait_complete()
s.poll()
return True
源码位置:linuxcnc/bin/axis:986-1015。
这说明 AXIS 侧前置条件分两层:
- UI 层用
s.poll()读当前状态,避免明显非法操作。 - task/motion 层仍会再次校验,非法命令会拒绝或报错。
3. Python C 扩展到 NML 命令
AXIS 使用 linuxcnc.command()。该对象由 linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc 暴露,关键映射如下:
| AXIS 调用 | C 扩展函数 | NML 命令 |
|---|---|---|
c.state(STATE_*) |
state() |
EMC_TASK_SET_STATE |
c.mode(MODE_*) |
mode() |
EMC_TASK_SET_MODE |
c.home(joint) |
home() |
EMC_JOINT_HOME |
c.auto(AUTO_RUN, line) |
emcauto() |
EMC_TASK_PLAN_RUN |
c.auto(AUTO_PAUSE) |
emcauto() |
EMC_TASK_PLAN_PAUSE |
c.auto(AUTO_RESUME) |
emcauto() |
EMC_TASK_PLAN_RESUME |
c.auto(AUTO_STEP) |
emcauto() |
EMC_TASK_PLAN_STEP |
源码依据:
mode()创建EMC_TASK_SET_MODE:linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:1691-1705state()创建EMC_TASK_SET_STATE:linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:1815-1829home()创建EMC_JOINT_HOME:linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:1941-1945emcauto()分发RUN/PAUSE/RESUME/STEP:linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:2093-2116
4. task 主循环的命令调度
task 主循环在 linuxcnc/src/emc/task/emctaskmain.cc:
emcCommandBuffer->read()
-> emcTaskPlan()
-> emcTaskExecute()
-> emcMotionUpdate(&emcStatus->motion)
-> emcTaskUpdate(&emcStatus->task)
-> 写 emcStatus 顶层 command/status/echo_serial_number
源码位置:linuxcnc/src/emc/task/emctaskmain.cc:3301-3478。
emcTaskPlan() 按三类状态进行门禁:
task.state:OFF、ESTOP、ESTOP_RESET、ONtask.mode:MANUAL、AUTO、MDItask.interpState:IDLE、READING、PAUSED、WAITING
因此,AXIS UI 的按钮条件必须与 emcTaskPlan() 的状态表一致,不能只看 runState 这类 Web 自定义状态。
5. 急停按钮
5.1 AXIS UI 调用链
源码:linuxcnc/bin/axis:2243-2248
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_RESET,表示解除急停。 - 其他任意状态:发送
STATE_ESTOP,进入急停。
5.2 NML/task 调用链
c.state(STATE_ESTOP or STATE_ESTOP_RESET)
-> emcmodule.cc state()
-> EMC_TASK_SET_STATE
-> emcTaskPlan()
-> emcTaskIssueCommand()
-> emcTaskSetState()
EMC_TASK_SET_STATE 被 task 作为 immediate command 接收:
- 非 ON 状态也允许接收:
emctaskmain.cc:795-829 - ON/MANUAL 允许:
emctaskmain.cc:897-956 - ON/AUTO/IDLE 允许:
emctaskmain.cc:1026-1075 - ON/AUTO/READING 允许:
emctaskmain.cc:1137-1174 - ON/AUTO/PAUSED 允许:
emctaskmain.cc:1206-1251
5.3 C++ task 状态处理
emcTaskSetState(ESTOP) 源码:linuxcnc/src/emc/task/emctask.cc:353-365
执行内容:
emcMotionAbort():中止 motion。emcSpindleAbort():中止主轴。emcAuxEstopOn():IO 急停输出置位。emcTrajDisable():下发 motion disable。- 关闭 flood coolant。
emcTaskAbort():清解释器队列、清 pause/step 状态。emcIoAbort(TASK_STATE_ESTOP)。emcJointUnhome(-2):清除VOLATILE_HOMEjoint 的 homed。emcTaskPlanSynch():同步解释器。
emcTaskSetState(ESTOP_RESET) 源码:linuxcnc/src/emc/task/emctask.cc:342-350
执行内容:
emcAuxEstopOff():解除 IO 急停。emcTaskAbort():清程序运行状态。emcIoAbort(TASK_STATE_ESTOP_RESET)。emcSpindleAbort()。emcTaskPlanSynch()。
5.4 状态记录方式
task 并不简单把 task.state = ESTOP 固定写死,而是周期性通过 determineState() 从 IO 和 motion 推导:
源码:linuxcnc/src/emc/task/emctask.cc:418-428
规则:
| IO 急停 | motion enabled | 推导状态 |
|---|---|---|
| true | 任意 | ESTOP |
| false | false | ESTOP_RESET |
| false | true | ON |
emcTaskUpdate() 每周期写入 stat->state:linuxcnc/src/emc/task/emctask.cc:688-699。
这意味着 Web/WASM 中不能只维护一个 estopActive 布尔值;应该同时维护:
- IO estop 状态;
- motion enable 状态;
- task 推导状态;
- volatile home 清除动作。
6. 上电/下电按钮
6.1 AXIS UI 调用链
源码:linuxcnc/bin/axis:2250-2260
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才能上电。 - 其他状态一律发送
STATE_OFF。 - 因此
STATE_ESTOP下点电源不是“上电”,而是进入 OFF/保持不可上电语义;必须先通过急停按钮解除急停到ESTOP_RESET。
6.2 task 调用链
c.state(STATE_ON/OFF)
-> EMC_TASK_SET_STATE
-> emcTaskSetState(ON/OFF)
emcTaskSetState(ON) 源码:linuxcnc/src/emc/task/emctask.cc:336-340
执行内容:
emcTrajEnable()。- 关闭 flood coolant。
emcTrajEnable() 源码:linuxcnc/src/emc/task/taskintf.cc:1396-1400
emcTrajEnable()
-> emcmotCommand.command = EMCMOT_ENABLE
-> usrmotWriteEmcmotCommand()
motion 侧 EMCMOT_ENABLE 源码:linuxcnc/src/emc/motion/command.c:1370-1384
执行内容:
- 如果 HAL
motion.enable输入为 false,报错:can't enable motion, enable input is false。 - 否则设置
emcmotInternal->enabling = 1,实际使能在 motion 控制周期中完成。
emcTaskSetState(OFF) 源码:linuxcnc/src/emc/task/emctask.cc:323-333
执行内容:
emcMotionAbort()。emcSpindleAbort()。emcTrajDisable()。emcIoAbort(TASK_STATE_OFF)。emcTaskAbort()。emcJointUnhome(-2)清 volatile home。emcTaskPlanSynch()。
motion 侧 EMCMOT_DISABLE 源码:linuxcnc/src/emc/motion/command.c:1357-1368
执行内容:
- 设置
emcmotInternal->enabling = 0。 - 对 inverse-only kinematics,还会退出 teleop/coord。
6.3 状态记录方式
上电后并不是由 task 直接写 STATE_ON,而是 motion 报告 enabled 后,determineState() 推导为 ON。
motion 状态更新路径:
motion command/control 周期更新 emcmotStatus.motionFlag
-> taskintf.cc emcMotionUpdate()
-> emcTrajUpdate()
-> stat->traj.enabled
-> emcTaskUpdate()
-> determineState()
关键源码:
emcMotionUpdate()读取emcmotStatus:linuxcnc/src/emc/task/taskintf.cc:2057-2102emcTrajUpdate()写stat->enabled:linuxcnc/src/emc/task/taskintf.cc:1639-1652determineState()推导状态:linuxcnc/src/emc/task/emctask.cc:418-428
7. Home 按钮
7.1 AXIS UI 调用链
Home All 源码:linuxcnc/bin/axis:2732-2740
def home_all_joints(event=None):
if not manual_ok(): return
ensure_mode(linuxcnc.MODE_MANUAL)
...
go_home(-1)
单 joint Home 源码:linuxcnc/bin/axis:2747-2767
关键条件:
manual_ok()必须为真,即 machine 为STATE_ON,解释器 idle 或 MDI 队列可接受。- 切到
MODE_MANUAL。 - 已 homed 时弹窗确认是否 re-home。
- 非 identity kinematics 下,单轴/单 joint 选择有额外限制。
- 最终
go_home(num)调c.home(num)。
7.2 NML/task/motion 调用链
c.home(-1 或 joint)
-> emcmodule.cc home()
-> EMC_JOINT_HOME
-> emcTaskPlan()
-> emcTaskIssueCommand()
-> emcJointHome()
-> EMCMOT_JOINT_HOME
-> motion command.c
-> do_home_joint()
-> homing.c 状态机
EMC_JOINT_HOME 只在 ON/MANUAL 中直接允许:linuxcnc/src/emc/task/emctaskmain.cc:897-956。
emcJointHome() 源码:linuxcnc/src/emc/task/taskintf.cc:796-805
执行内容:
- 检查 joint 范围。
- 下发
EMCMOT_JOINT_HOME。
motion 侧 EMCMOT_JOINT_HOME 源码:linuxcnc/src/emc/motion/command.c:1411-1439
motion 前置条件:
emcmotStatus->motion_state == EMCMOT_MOTION_FREE,必须处于 joint/free 模式。motion.homing-inhibit不能为 true。- 当前不能已有 homing 流程在跑。
GET_MOTION_ENABLE_FLAG()必须为 true。- 负 joint 表示 home all。
7.3 homing 状态机
do_home_joint() 最终进入 linuxcnc/src/emc/motion/homing.c。
核心状态记录:
H[joint].homingH[joint].homedH[joint].home_stateH[joint].joint_in_sequencejoint.free_tp.enable
输出 HAL pin:
源码:linuxcnc/src/emc/motion/homing.c:541-573
joint.N.homingjoint.N.homedjoint.N.home-statejoint.N.index-enable
完成逻辑:
源码:linuxcnc/src/emc/motion/homing.c:1376-1385
homing = 0homed = 1home_state = HOME_IDLEjoint_in_sequence = 0
异常中止:
源码:linuxcnc/src/emc/motion/homing.c:1387-1397
- 所有 joint
homing = 0 - 所有 joint
homed = 0 - 所有
free_tp.enable = 0 home_state = HOME_IDLE
7.4 状态记录方式
task 通过 emcMotionUpdate() 将 motion homed 状态复制到 emcStatus->motion.joint[n].homed,AXIS 的 s.homed[n] 来自这里。
task 的 all_homed() 直接查 emcStatus->motion.joint[i].homed,源码:linuxcnc/src/emc/task/emctaskmain.cc:151-152。
8. 执行 Run 按钮
8.1 AXIS UI 调用链
源码:linuxcnc/bin/axis:2379-2393
def task_run(*event):
res = run_warn()
...
ensure_mode(linuxcnc.MODE_AUTO)
c.auto(linuxcnc.AUTO_RUN, program_start_line)
program_start_line = 0
AXIS UI 层条件:
- 会执行
run_warn(),处理文件变更、保存、运行前警告等。 ensure_mode(MODE_AUTO)切入 AUTO。- 然后发送
AUTO_RUN。
真正硬条件在 task 层:
- machine 必须 ON。
- AUTO 模式下允许。
- 未 homed 且没有
no_force_homing时拒绝。 - 程序文件应已打开;未打开时会尝试打开
emcStatus->task.file。
8.2 task 调用链
c.auto(AUTO_RUN, line)
-> EMC_TASK_PLAN_RUN
-> emcTaskPlan() 按状态门禁
-> emcTaskIssueCommand()
-> case EMC_TASK_PLAN_RUN
EMC_TASK_PLAN_RUN 在 ON/AUTO/IDLE 中允许:linuxcnc/src/emc/task/emctaskmain.cc:1015-1075。
核心处理源码:linuxcnc/src/emc/task/emctaskmain.cc:2318-2335
执行内容:
- 如果未 homed 且
!no_force_homing,报错:Can't run a program when not homed。 - 清
motion.traj.single_stepping。 - 清
stepping、steppingWait。 - 必要时
emcTaskPlanOpen(emcStatus->task.file)。 - 记录
programStartLine = run_msg->line。 - 设置
emcStatus->task.interpState = READING。 - 设置
emcStatus->task.task_paused = 0。
8.3 解释器和 motion 下发
Run 后 emcTaskPlan() 进入 READING,周期性调用 readahead 逻辑读取 G-code。解释器通过 canonical 函数把运动命令排入 interp_list,emcTaskExecute() 再把 motion 命令下发到 motion。
运动命令如直线/圆弧最终在 motion command.c 中以 EMCMOT_SET_LINE、EMCMOT_SET_CIRCLE 等处理。运动状态通过 emcmotStatus.id、depth、activeDepth、paused、current_vel 等回传。
8.4 状态记录方式
Run 后主要状态变化:
| 字段 | 变化 |
|---|---|
task.mode |
AUTO |
task.interpState |
READING |
task.task_paused |
0 |
motion.traj.single_stepping |
0 |
motion.traj.queue/activeQueue |
随 motion 队列变化 |
motion.traj.id/tag |
跟随当前运动段与源代码 state tag |
emcStatus->task.status 顶层状态由 emctaskmain.cc:3458-3476 合成:有错误为 ERROR,全部完成且解释器 idle 为 DONE,否则为 EXEC。
9. 暂停 Pause 按钮
9.1 AXIS UI 调用链
源码:linuxcnc/bin/axis:2402-2406
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)
AXIS Pause 的严格条件:
task_mode == MODE_AUTOinterp_state in (INTERP_READING, INTERP_WAITING)- 不是仅凭
runState == running
工具栏合并暂停/恢复 task_pauseresume() 源码:linuxcnc/bin/axis:2433-2443
- 仅
MODE_AUTO或MODE_MDI可操作。 - 如果
s.paused为真,发送 resume。 - 否则如果解释器不是 idle,发送 pause。
9.2 task/motion 调用链
c.auto(AUTO_PAUSE)
-> EMC_TASK_PLAN_PAUSE
-> emcTaskPlan()
-> emcTaskIssueCommand()
-> emcTrajPause()
-> EMCMOT_PAUSE
-> tpPause()
task 处理源码:linuxcnc/src/emc/task/emctaskmain.cc:2337-2345
执行内容:
emcTrajPause()- 若当前不是
PAUSED,保存interpResumeState = interpState task.interpState = PAUSEDtask.task_paused = 1
emcTrajPause() 源码:linuxcnc/src/emc/task/taskintf.cc:1417-1421
motion 侧 EMCMOT_PAUSE 源码:linuxcnc/src/emc/motion/command.c:1234-1240
执行内容:
tpPause(&coord_tp)emcmotStatus->paused = 1
9.3 状态记录方式
Pause 后必须同时体现:
task.interpState = PAUSEDtask.task_paused = 1motion.traj.paused = 1interpResumeState保存暂停前的READING或WAITING
Web 项目必须避免只改 UI runState = paused,否则 resume/step 会缺少 LinuxCNC 必需的恢复上下文。
10. 单步 Step 按钮
10.1 AXIS UI 调用链
源码:linuxcnc/bin/axis:2395-2400
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)
AXIS Step 的含义不是“前端播放下一帧”,而是向 task 发送 EMC_TASK_PLAN_STEP。task 根据解释器状态决定:
- AUTO/IDLE:先启动程序,再立刻暂停,进入单步准备态。
- AUTO/READING 或 WAITING:设置 single stepping,下一段读入/执行后停住。
- AUTO/PAUSED:若 motion 队列中有暂停运动,发
EMCMOT_STEP让 motion 走到下一段;否则恢复解释器状态继续读。
10.2 task 处理
AUTO/IDLE 中 STEP 源码:linuxcnc/src/emc/task/emctaskmain.cc:1078-1091
执行内容:
- 构造
taskPlanRunCmd.line = 0 - 调
emcTaskIssueCommand(&taskPlanRunCmd)启动程序 emcTrajPause()- 保存
interpResumeState task.interpState = PAUSEDtask.task_paused = 1
AUTO/READING 中 STEP 源码:linuxcnc/src/emc/task/emctaskmain.cc:1178-1182
执行内容:
motion.traj.single_stepping = 1stepping = 1steppingWait = 0
AUTO/PAUSED 中 STEP 源码:linuxcnc/src/emc/task/emctaskmain.cc:1254-1266
执行内容:
motion.traj.single_stepping = 1stepping = 1- 如果
motion.traj.paused && queue > 0,调用emcTrajStep() - 否则
task.interpState = interpResumeState task.task_paused = 1
AUTO/WAITING 中 STEP 源码:linuxcnc/src/emc/task/emctaskmain.cc:1330-1334
执行内容同 READING:设置单步标志。
10.3 motion 处理
emcTrajStep() 源码:linuxcnc/src/emc/task/taskintf.cc:1438-1442
motion 侧 EMCMOT_STEP 源码:linuxcnc/src/emc/motion/command.c:1265-1273
处理逻辑:
- 如果 motion 当前 paused:
- 记录
idForStep = emcmotStatus->id emcmotStatus->stepping = 1- resume 轨迹规划器,使其执行到 id 变化或下一步边界再停。
- 记录
10.4 状态记录方式
单步相关字段:
- task 侧静态变量:
stepping、steppingWait - task 可见状态:
emcStatus->motion.traj.single_stepping - motion 状态:
emcmotStatus->stepping、idForStep、paused - 解释器状态:
PAUSED或恢复到interpResumeState
Web 中若只实现“数组 frameIndex++”,不能等价于 LinuxCNC Step。正确做法是把 Step 作为 task 命令,驱动 task/motion 的 pause/step 状态机。
11. Resume 补充
虽然用户重点列出“急停、上电、Home、执行、暂停、单步执行”,但暂停机制必须包含 Resume 才完整。
AXIS task_resume() 源码:linuxcnc/bin/axis:2424-2431
条件:
s.paused为真。task_mode是AUTO或MDI。- 发送
AUTO_RESUME。
task 处理源码:linuxcnc/src/emc/task/emctaskmain.cc:2369-2376
执行内容:
emcTrajResume()task.interpState = interpResumeStatetask.task_paused = 0motion.traj.single_stepping = 0- 清
stepping、steppingWait
motion 侧 EMCMOT_RESUME 源码:linuxcnc/src/emc/motion/command.c:1256-1263
执行内容:
emcmotStatus->stepping = 0tpResume()emcmotStatus->paused = 0
12. 按钮前置条件汇总
| 按钮 | AXIS UI 前置条件 | task/motion 硬条件 | 执行后状态 |
|---|---|---|---|
| 急停 | 无;读 s.task_state 决定 reset 或 estop |
EMC_TASK_SET_STATE 在多数状态允许 |
ESTOP 分支 abort、disable、IO estop on;reset 分支 IO estop off、abort、同步解释器 |
| 上电 | 只有 STATE_ESTOP_RESET 发送 ON,否则发送 OFF |
ON 要 motion enable 输入为真;OFF 总是 abort/disable | ON 后由 motion.traj.enabled 推导 STATE_ON;OFF 后推导 ESTOP_RESET 或 OFF 语义并清 volatile home |
| Home | manual_ok();切 MODE_MANUAL;必要时 re-home 确认 |
motion 必须 FREE、enabled、未 homing、未 inhibit | joint.N.homing 过程态;完成 joint.N.homed=1;失败清 homed |
| Run | run_warn();切 MODE_AUTO |
ON/AUTO/IDLE;已 homed 或 no_force_homing;程序打开 | interpState=READING,task_paused=0,motion 队列开始增长 |
| Pause | MODE_AUTO 且 INTERP_READING/WAITING |
EMC_TASK_PLAN_PAUSE;motion EMCMOT_PAUSE |
interpState=PAUSED,task_paused=1,motion.paused=1,保存 resume state |
| Step | 切 MODE_AUTO;非 idle 时可先 run_warn |
按 AUTO/IDLE/READING/PAUSED/WAITING 分支处理 | 设置 single_stepping;必要时 EMCMOT_STEP;保持/进入 paused |
| Resume | paused 且 MODE_AUTO/MDI |
EMC_TASK_PLAN_RESUME;motion EMCMOT_RESUME |
恢复 interpResumeState,清 task_paused/single_stepping/stepping |
13. WASM-port 完善建议
当前 wasm-port 已有 task/HAL WASM 入口:
wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_task_hal_wasm.cppwasm-port/runtime/sdk/src/linuxcnc-task-hal.js
但是 linuxcnc_task_hal_wasm.cpp 仍是 linuxcnc_task_motion_hal_wasm_phase4_minimal,存在明显过渡实现:
- 用字符串搜索和手写 JSON 解析命令。
- 自己维护
TaskRuntime.state/mode/interp_state/task_paused/single_stepping。 - 从 G-code 文本中用简单规则派生 motion,存在 G-code 语义重实现风险。
readiness()中仍标记nativeTaskReady:false、nativeHalSyncReady:false。
这与 wasm-port/AGENTS.md 的要求冲突风险较高:项目应复用 LinuxCNC source,不应扩展项目自写 CNC 语义。
建议按以下阶段完善。
13.1 第一阶段:把状态枚举和门禁表固化为 LinuxCNC 镜像
目标:先不改大架构,但移除随意字符串状态。
建议:
- 在
runtime/core/linuxcnc_wrap增加 task 状态模型头文件,例如linuxcnc_task_state_model.hh。 - 用与 LinuxCNC 一致的枚举名:
ESTOP、ESTOP_RESET、ON、MANUAL、AUTO、MDI、IDLE、READING、PAUSED、WAITING。 - 实现一个
task_accepts_command(state, mode, interp, command),逐项映射emcTaskPlan()的允许表。 - 所有
lctask_send_command_json()先走该门禁表,返回明确错误码和错误文本。 - 增加测试覆盖:
- ESTOP 下仅允许 state/mode/config 等 immediate 命令。
- ON/MANUAL 下允许 HOME,不允许 AUTO RUN。
- ON/AUTO/IDLE 下允许 RUN/STEP。
- ON/AUTO/READING 下允许 PAUSE/STEP,不允许 HOME。
- ON/AUTO/PAUSED 下允许 RESUME/STEP。
13.2 第二阶段:替换手写 G-code motion 派生
当前 enqueue_linear_move_from_line() 和 Web 侧 buildSourceLineMotionSegments() 都会从 G-code 行解析轴和 feed,这属于语义漂移高风险点。
正确方向:
- G-code 到 canonical motion 必须由 vendored LinuxCNC interpreter 产生。
- task/HAL runtime 只消费 LinuxCNC canonical 输出或已验证的 motion plan,不自行解释 G-code。
- 对没有 motion plan 的程序,应返回“需要 interpreter plan”错误,而不是 fallback 解析
G0/G1。 buildTaskHalProgramMotionPlan()可保留为 host 边界适配器,但输入必须来自 LinuxCNC interpreter runtime 的 canonical events,而不是源码行正则解析。
13.3 第三阶段:引入 vendored task/motion 源码闭环
最终目标不是仿照 emctaskmain.cc 写一份 JS/C++ 状态机,而是逐步把 vendored LinuxCNC task/motion 代码纳入 WASM:
- 将
emc/nml_intf/emc_nml.hh中的命令/状态结构作为 WASM 内部 ABI 参考。 - 用内存队列替代 NML transport,但保留
EMC_TASK_*/EMCMOT_*命令类型。 - 将
taskintf.cc的emcTrajEnable/Pause/Resume/Step/Home路由接到linuxcnc_motion_runtime.c。 - 将
emcTaskSetState()、emcTaskSetMode()、emcTaskAbort()的语义直接迁移或编译进 WASM。 - motion runtime 应逐步引入
command.c、homing.c、control.c中真实状态更新,而不是只暴露“看起来相同”的 JSON status。
13.4 第四阶段:状态发布完全对齐 emcStatus
WASM SDK readStatus() 应输出与 Python linuxcnc.stat() 结构同构的字段:
task.statetask.modetask.interpStatetask.execStatetask.taskPausedmotion.traj.enabledmotion.traj.pausedmotion.traj.singleSteppingmotion.traj.queuemotion.traj.idmotion.joint[n].homedmotion.joint[n].homingmotion.joint[n].homeStateechoSerialNumbercommandType
这样 Web 项目可以直接消费 LinuxCNC 状态,而不是维护第二套状态。
14. web-rtcp-5axis-xyzbc-trt-sim-plan 完善建议
当前 Web 项目已有:
app/src/state/linuxcnc-task-policy.jsapp/src/state/store.jsapp/src/runtime/linuxcnc-task-hal-runtime.js- browser/node 对按钮路径的验证
这已经完成一部分 AXIS 对标,但还需要继续收敛。
14.1 状态源优先级必须调整
建议状态优先级:
taskHalStatus.ui/task/motion 原生状态
> LinuxCNC interpreter canonical events
> Web UI 派生状态
Web store 中 machine.taskState/mode/interpState/runState 应逐渐变成 task/HAL 状态的投影,而不是独立真相源。
14.2 按钮 action 应发送 LinuxCNC 命令,不直接改业务状态
按钮处理建议统一为:
UI click
-> gateLinuxCncTaskAction()
-> taskHalRuntime.sendCommand({ type: "EMC_TASK_*" })
-> taskHalRuntime.runCycles()
-> taskHalRuntime.readStatus()
-> store 接收 STATUS_SYNC
-> UI 渲染
避免:
- click 后直接
runState = "running"; - click 后直接
machine.allHomed = true; - pause/step 直接操作播放帧;
- power 直接改
powerOn而不等 motion enabled/status。
14.3 补齐 Home 状态过程态
Web 当前常见实现是 HOME 后立即 allHomed=true。LinuxCNC 真实机制是:
EMC_JOINT_HOME
-> joint.N.homing = true
-> home_state 多阶段变化
-> joint.N.homed = true
建议 Web 状态增加:
machine.joints[n].homingmachine.joints[n].homedmachine.joints[n].homeStatemachine.homingActivemachine.allHomed
按钮门禁:
- homing 过程中禁止再次 Home。
- Run 前使用
allHomed || noForceHoming。 - OFF/ESTOP 时清 volatile home,而不是无条件清所有 home;如果当前模型无法区分 volatile,先在文档和状态字段中标注“volatile home 简化”。
14.4 补齐 Step 的 LinuxCNC 语义
Web 需要区分三种 Step:
- 程序 idle 首次 step:等价于 RUN 后立即 PAUSE。
- 程序 running 中 step:设置 single stepping,等待下一段边界停住。
- 程序 paused 中 step:若 motion queue 有内容,发送 motion step;否则恢复 interpreter 读下一段。
建议在 linuxcnc-task-policy.js 中把 STEP 门禁拆开:
canStepFromIdlecanStepWhileReadingcanStepWhilePausedcanStepWhileWaiting
并在 status 中显示:
task.singleSteppingmotion.steppingmotion.idForStepmotion.paused
14.5 Pause/Resume 继续保持 AXIS 门禁
已完成的严格对标应继续保留:
PAUSE:仅STATE_ON + MODE_AUTO + INTERP_READING/WAITING。PAUSE_RESUME:仅MODE_AUTO/MDI;paused 时 resume;非 idle 时 pause。RESUME:必须paused && MODE_AUTO/MDI。
后续测试不要只断言按钮可点击,应断言 task/HAL status:
- pause 后
task.interpState=PAUSED - pause 后
task.taskPaused=true - pause 后
motion.paused=true - resume 后恢复
interpResumeState - step 后
singleStepping=true
14.6 5 轴 XYZBC-TRT 特有要求
5 轴仿真不应把主按钮逻辑和 RTCP/kinematics 逻辑混在一起。建议分层:
- task/motion 状态机:只负责开关机、home、run、pause、step。
- kinematics/RTCP:只负责 XYZBC-TRT 位置变换和 TCP 显示。
- interpreter/remap:负责 M428/M429/M430、switchkins、G-code 语义。
- UI:只渲染状态并发送命令。
Run 前置条件除 LinuxCNC 通用条件外,还应检查:
- 选定 profile 的 INI 已加载;
- machine files 已 staged;
- 当前 G-code 已 open 到 task/HAL session;
- kinematics runtime 与 task/HAL runtime readiness 均为 true;
- 如果程序依赖 switchkins/remap,HAL
motion.switchkins-type同步状态应可观测。
15. 推荐验证矩阵
15.1 LinuxCNC 行为矩阵
用本地 LinuxCNC 源码为基准建立 golden matrix:
| 初始状态 | 命令 | 期望 |
|---|---|---|
| ESTOP/MANUAL/IDLE | ESTOP button | STATE_ESTOP_RESET |
| ESTOP_RESET/MANUAL/IDLE | Power | STATE_ON |
| ESTOP/MANUAL/IDLE | Power | 不上电,走 OFF 分支 |
| ON/MANUAL/IDLE | Home All | joint.*.homing -> homed |
| ON/AUTO/IDLE + unhomed | Run | 拒绝,提示未 homed |
| ON/AUTO/IDLE + homed | Run | interpState=READING |
| ON/AUTO/READING | Pause | interpState=PAUSED、motion.paused=true |
| ON/AUTO/PAUSED | Resume | 恢复 reading/waiting |
| ON/AUTO/IDLE + homed | Step | run + pause,single step 准备 |
| ON/AUTO/PAUSED + queue>0 | Step | EMCMOT_STEP,执行下一段后停 |
15.2 wasm-port 测试建议
新增或扩展:
tests/wasm/node/verify_task_state_matrix.mjstests/wasm/node/verify_task_home_state_machine.mjstests/wasm/node/verify_task_pause_resume_step.mjs
每个测试不要只看返回码,要读 lctask_read_status_json():
- task 字段;
- motion 字段;
- HAL pins;
- events;
- command echo。
15.3 Web 测试建议
在现有 verify_xyzbc_trt_web_app.mjs 和 browser smoke 上补齐:
- ESTOP/RESET/POWER 不允许绕过;
- Home 有过程态;
- Run 未 homed 拒绝;
- Pause idle 拒绝;
- Pause manual 拒绝;
- Step idle 首次启动后暂停;
- Step paused 推进一段;
- Resume 清 single stepping;
- Store 状态与 taskHalStatus 一致。
16. 实施优先级
建议按以下顺序推进:
wasm-port先消除 task/HAL runtime 中的 G-code 正则解析 fallback,要求 motion plan 必须来自 LinuxCNC interpreter canonical output。wasm-port把lctask_send_command_json()改为显式命令结构解析和 LinuxCNC 状态门禁表。wasm-port补齐task/motion/joint状态字段,使readStatus()接近linuxcnc.stat()。- Web store 改为 taskHalStatus 投影源,减少直接改
runState/machine的分支。 - Web Home/Step 加过程态和真实 task/motion 状态断言。
- 再继续推进 vendored
emctask.cc/taskintf.cc/command.c/homing.c的更直接编译或等价适配。
最终目标:Web 按钮只是 AXIS 的浏览器外壳,状态机和 CNC 语义来自 LinuxCNC 源码或其受控 WASM 适配层。