Files
cnc_wams/项目分析/AXIS主控制按钮调用链与WASM完善指南.md
2026-07-07 16:46:39 -04:00

30 KiB
Raw Blame History

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_ESTOPSTATE_ESTOP_RESETSTATE_ON 等。
  • emcStatus->task.mode:任务模式,MODE_MANUALMODE_AUTOMODE_MDI
  • emcStatus->task.interpState:解释器状态,INTERP_IDLEINTERP_READINGINTERP_PAUSEDINTERP_WAITING
  • emcStatus->task.task_pausedtask 层暂停标志。
  • emcStatus->motion.traj.pausedmotion 轨迹规划器暂停标志。
  • emcStatus->motion.traj.single_steppingtask/GUI 可见的单步执行标志。
  • emcStatus->motion.joint[n].homed:每个 joint 的回零状态。
  • emcStatus->motion.traj.enabledmotion 是否上使能,用于推导 task 是否 STATE_ON

需要特别注意LinuxCNC 的 task 层主要是 C++motion 层主要是 C。用户所说的 C++ 实现,在源码中实际跨越 emctask*.cctaskintf.cc 和 motion 的 command.choming.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 侧前置条件分两层:

  1. UI 层用 s.poll() 读当前状态,避免明显非法操作。
  2. 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_MODElinuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:1691-1705
  • state() 创建 EMC_TASK_SET_STATElinuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:1815-1829
  • home() 创建 EMC_JOINT_HOMElinuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc:1941-1945
  • emcauto() 分发 RUN/PAUSE/RESUME/STEPlinuxcnc/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() 按三类状态进行门禁:

  1. task.stateOFFESTOPESTOP_RESETON
  2. task.modeMANUALAUTOMDI
  3. task.interpStateIDLEREADINGPAUSEDWAITING

因此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_HOME joint 的 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->statelinuxcnc/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() 读取 emcmotStatuslinuxcnc/src/emc/task/taskintf.cc:2057-2102
  • emcTrajUpdate()stat->enabledlinuxcnc/src/emc/task/taskintf.cc:1639-1652
  • determineState() 推导状态: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].homing
  • H[joint].homed
  • H[joint].home_state
  • H[joint].joint_in_sequence
  • joint.free_tp.enable

输出 HAL pin

源码:linuxcnc/src/emc/motion/homing.c:541-573

  • joint.N.homing
  • joint.N.homed
  • joint.N.home-state
  • joint.N.index-enable

完成逻辑:

源码:linuxcnc/src/emc/motion/homing.c:1376-1385

  • homing = 0
  • homed = 1
  • home_state = HOME_IDLE
  • joint_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].homedAXIS 的 s.homed[n] 来自这里。

taskall_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
  • steppingsteppingWait
  • 必要时 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_listemcTaskExecute() 再把 motion 命令下发到 motion。

运动命令如直线/圆弧最终在 motion command.c 中以 EMCMOT_SET_LINEEMCMOT_SET_CIRCLE 等处理。运动状态通过 emcmotStatus.iddepthactiveDepthpausedcurrent_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_AUTO
  • interp_state in (INTERP_READING, INTERP_WAITING)
  • 不是仅凭 runState == running

工具栏合并暂停/恢复 task_pauseresume() 源码:linuxcnc/bin/axis:2433-2443

  • MODE_AUTOMODE_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 = PAUSED
  • task.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 = PAUSED
  • task.task_paused = 1
  • motion.traj.paused = 1
  • interpResumeState 保存暂停前的 READINGWAITING

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 = PAUSED
  • task.task_paused = 1

AUTO/READING 中 STEP 源码:linuxcnc/src/emc/task/emctaskmain.cc:1178-1182

执行内容:

  • motion.traj.single_stepping = 1
  • stepping = 1
  • steppingWait = 0

AUTO/PAUSED 中 STEP 源码:linuxcnc/src/emc/task/emctaskmain.cc:1254-1266

执行内容:

  • motion.traj.single_stepping = 1
  • stepping = 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 侧静态变量:steppingsteppingWait
  • task 可见状态:emcStatus->motion.traj.single_stepping
  • motion 状态:emcmotStatus->steppingidForSteppaused
  • 解释器状态: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_modeAUTOMDI
  • 发送 AUTO_RESUME

task 处理源码:linuxcnc/src/emc/task/emctaskmain.cc:2369-2376

执行内容:

  • emcTrajResume()
  • task.interpState = interpResumeState
  • task.task_paused = 0
  • motion.traj.single_stepping = 0
  • steppingsteppingWait

motion 侧 EMCMOT_RESUME 源码:linuxcnc/src/emc/motion/command.c:1256-1263

执行内容:

  • emcmotStatus->stepping = 0
  • tpResume()
  • emcmotStatus->paused = 0

12. 按钮前置条件汇总

按钮 AXIS UI 前置条件 task/motion 硬条件 执行后状态
急停 无;读 s.task_state 决定 reset 或 estop EMC_TASK_SET_STATE 在多数状态允许 ESTOP 分支 abort、disable、IO estop onreset 分支 IO estop off、abort、同步解释器
上电 只有 STATE_ESTOP_RESET 发送 ON否则发送 OFF ON 要 motion enable 输入为真OFF 总是 abort/disable ON 后由 motion.traj.enabled 推导 STATE_ONOFF 后推导 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=READINGtask_paused=0motion 队列开始增长
Pause MODE_AUTOINTERP_READING/WAITING EMC_TASK_PLAN_PAUSEmotion EMCMOT_PAUSE interpState=PAUSEDtask_paused=1motion.paused=1,保存 resume state
Step MODE_AUTO;非 idle 时可先 run_warn 按 AUTO/IDLE/READING/PAUSED/WAITING 分支处理 设置 single_stepping;必要时 EMCMOT_STEP;保持/进入 paused
Resume pausedMODE_AUTO/MDI EMC_TASK_PLAN_RESUMEmotion 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.cpp
  • wasm-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:falsenativeHalSyncReady:false

这与 wasm-port/AGENTS.md 的要求冲突风险较高:项目应复用 LinuxCNC source不应扩展项目自写 CNC 语义。

建议按以下阶段完善。

13.1 第一阶段:把状态枚举和门禁表固化为 LinuxCNC 镜像

目标:先不改大架构,但移除随意字符串状态。

建议:

  1. runtime/core/linuxcnc_wrap 增加 task 状态模型头文件,例如 linuxcnc_task_state_model.hh
  2. 用与 LinuxCNC 一致的枚举名:ESTOPESTOP_RESETONMANUALAUTOMDIIDLEREADINGPAUSEDWAITING
  3. 实现一个 task_accepts_command(state, mode, interp, command),逐项映射 emcTaskPlan() 的允许表。
  4. 所有 lctask_send_command_json() 先走该门禁表,返回明确错误码和错误文本。
  5. 增加测试覆盖:
    • 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这属于语义漂移高风险点。

正确方向:

  1. G-code 到 canonical motion 必须由 vendored LinuxCNC interpreter 产生。
  2. task/HAL runtime 只消费 LinuxCNC canonical 输出或已验证的 motion plan不自行解释 G-code。
  3. 对没有 motion plan 的程序,应返回“需要 interpreter plan”错误而不是 fallback 解析 G0/G1
  4. buildTaskHalProgramMotionPlan() 可保留为 host 边界适配器,但输入必须来自 LinuxCNC interpreter runtime 的 canonical events而不是源码行正则解析。

13.3 第三阶段:引入 vendored task/motion 源码闭环

最终目标不是仿照 emctaskmain.cc 写一份 JS/C++ 状态机,而是逐步把 vendored LinuxCNC task/motion 代码纳入 WASM

  1. emc/nml_intf/emc_nml.hh 中的命令/状态结构作为 WASM 内部 ABI 参考。
  2. 用内存队列替代 NML transport但保留 EMC_TASK_* / EMCMOT_* 命令类型。
  3. taskintf.ccemcTrajEnable/Pause/Resume/Step/Home 路由接到 linuxcnc_motion_runtime.c
  4. emcTaskSetState()emcTaskSetMode()emcTaskAbort() 的语义直接迁移或编译进 WASM。
  5. motion runtime 应逐步引入 command.choming.ccontrol.c 中真实状态更新,而不是只暴露“看起来相同”的 JSON status。

13.4 第四阶段:状态发布完全对齐 emcStatus

WASM SDK readStatus() 应输出与 Python linuxcnc.stat() 结构同构的字段:

  • task.state
  • task.mode
  • task.interpState
  • task.execState
  • task.taskPaused
  • motion.traj.enabled
  • motion.traj.paused
  • motion.traj.singleStepping
  • motion.traj.queue
  • motion.traj.id
  • motion.joint[n].homed
  • motion.joint[n].homing
  • motion.joint[n].homeState
  • echoSerialNumber
  • commandType

这样 Web 项目可以直接消费 LinuxCNC 状态,而不是维护第二套状态。

14. web-rtcp-5axis-xyzbc-trt-sim-plan 完善建议

当前 Web 项目已有:

  • app/src/state/linuxcnc-task-policy.js
  • app/src/state/store.js
  • app/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].homing
  • machine.joints[n].homed
  • machine.joints[n].homeState
  • machine.homingActive
  • machine.allHomed

按钮门禁:

  • homing 过程中禁止再次 Home。
  • Run 前使用 allHomed || noForceHoming
  • OFF/ESTOP 时清 volatile home而不是无条件清所有 home如果当前模型无法区分 volatile先在文档和状态字段中标注“volatile home 简化”。

14.4 补齐 Step 的 LinuxCNC 语义

Web 需要区分三种 Step

  1. 程序 idle 首次 step等价于 RUN 后立即 PAUSE。
  2. 程序 running 中 step设置 single stepping等待下一段边界停住。
  3. 程序 paused 中 step若 motion queue 有内容,发送 motion step否则恢复 interpreter 读下一段。

建议在 linuxcnc-task-policy.js 中把 STEP 门禁拆开:

  • canStepFromIdle
  • canStepWhileReading
  • canStepWhilePaused
  • canStepWhileWaiting

并在 status 中显示:

  • task.singleStepping
  • motion.stepping
  • motion.idForStep
  • motion.paused

14.5 Pause/Resume 继续保持 AXIS 门禁

已完成的严格对标应继续保留:

  • PAUSE:仅 STATE_ON + MODE_AUTO + INTERP_READING/WAITING
  • PAUSE_RESUME:仅 MODE_AUTO/MDIpaused 时 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 逻辑混在一起。建议分层:

  1. task/motion 状态机只负责开关机、home、run、pause、step。
  2. kinematics/RTCP只负责 XYZBC-TRT 位置变换和 TCP 显示。
  3. interpreter/remap负责 M428/M429/M430、switchkins、G-code 语义。
  4. UI只渲染状态并发送命令。

Run 前置条件除 LinuxCNC 通用条件外,还应检查:

  • 选定 profile 的 INI 已加载;
  • machine files 已 staged
  • 当前 G-code 已 open 到 task/HAL session
  • kinematics runtime 与 task/HAL runtime readiness 均为 true
  • 如果程序依赖 switchkins/remapHAL 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=PAUSEDmotion.paused=true
ON/AUTO/PAUSED Resume 恢复 reading/waiting
ON/AUTO/IDLE + homed Step run + pausesingle step 准备
ON/AUTO/PAUSED + queue>0 Step EMCMOT_STEP,执行下一段后停

15.2 wasm-port 测试建议

新增或扩展:

  • tests/wasm/node/verify_task_state_matrix.mjs
  • tests/wasm/node/verify_task_home_state_machine.mjs
  • tests/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. 实施优先级

建议按以下顺序推进:

  1. wasm-port 先消除 task/HAL runtime 中的 G-code 正则解析 fallback要求 motion plan 必须来自 LinuxCNC interpreter canonical output。
  2. wasm-portlctask_send_command_json() 改为显式命令结构解析和 LinuxCNC 状态门禁表。
  3. wasm-port 补齐 task/motion/joint 状态字段,使 readStatus() 接近 linuxcnc.stat()
  4. Web store 改为 taskHalStatus 投影源,减少直接改 runState/machine 的分支。
  5. Web Home/Step 加过程态和真实 task/motion 状态断言。
  6. 再继续推进 vendored emctask.cc/taskintf.cc/command.c/homing.c 的更直接编译或等价适配。

最终目标Web 按钮只是 AXIS 的浏览器外壳,状态机和 CNC 语义来自 LinuxCNC 源码或其受控 WASM 适配层。