docs: add axis run process working set

This commit is contained in:
wangdequan
2026-07-07 16:46:39 -04:00
parent 8b6be369ff
commit 39b495cf16
672 changed files with 280923 additions and 59 deletions

View File

@@ -3226,3 +3226,181 @@ Pause 修复已提交并推送到云仓库 `master`,提交号为 `58854f9`。
### 结论
业务提交 `4ba45b2 feat: complete working8 pause semantics` 已成功推送到云仓库 `origin/master`。本次日志已按项目规则追加,随后将单独提交并推送日志提交,确保远端也包含本次执行记录。
## 2026-07-07 10:38 EDT
### 提问
用户要求通过 HTTPS 方式发布网站到服务器 `82.156.24.101`SSH 用户为 `ubuntu`,密码已在日志中脱敏,说明 `8080-8099` 端口已经开放,要求使用 `8092` 端口,域名为 `codexcnc.com`
### 执行过程
1. 读取项目根目录结构,确认当前仓库路径为 `/home/mes123456/cnc_wams`,项目中存在多个 Web 目录。
2. 查看 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/package.json`,确认站点名称为 `web-rtcp-5axis-xyzbc-trt-sim`,构建命令为 `npm run build`,构建脚本为 `node scripts/build-static.mjs`
3. 查看 `web-rtcp-5axis-sim-plan/app/package.json` 和两个项目 README对比后确认本次应发布的站点是 `web-rtcp-5axis-xyzbc-trt-sim-plan/app`,其静态产物目录为 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist`
4. 查看 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/scripts/build-static.mjs`,确认构建会复制 `index.html``src`、LinuxCNC 配置、参考资源和 WASM 运行时资源到 `dist`
5. 查看 `gptlog-process/gpdlog.md` 中今天早前的发布记录,确认该站点之前已经配置过 `codexcnc.com`、Nginx、Let's Encrypt 证书和 `8092` HTTPS 服务;本次仍重新验证并重新发布最新构建产物。
6. 执行 `getent ahostsv4 codexcnc.com`,确认域名 `codexcnc.com` 解析到 `82.156.24.101`
7. 执行 `curl -k -I https://codexcnc.com:8092/`,确认 `8092` 端口当前已返回 `HTTP/2 200`
8. 执行 `curl -I https://codexcnc.com/`,发现标准 `443` 端口公网访问在响应阶段被重置;因此后续以用户指定的 `8092` 端口作为发布目标,并额外检查 443 原因。
9. 确认本机存在 `/usr/bin/sshpass`,随后使用用户提供的 SSH 信息登录远端服务器;日志中不记录明文密码。
10. 在远端执行系统检查,确认服务器主机名为 `VM-0-3-ubuntu`,系统为 Ubuntu 24.04 LTSNginx 版本为 `nginx/1.24.0 (Ubuntu)`Nginx 状态为 active。
11. 在远端执行 `sudo -n true`,确认当前用户可无交互执行 sudo。
12. 在远端执行 `ss -ltnp` 检查监听端口,确认 `80``443``8092` 均由 Nginx 监听。
13. 执行 `openssl s_client -connect codexcnc.com:8092 -servername codexcnc.com`,确认 `8092` 端口证书主体为 `CN=codexcnc.com`,签发方为 Let's Encrypt `YE1`,有效期为 `Jul 7 11:53:17 2026 GMT``Oct 5 11:53:16 2026 GMT`
14. 执行 `npm --prefix web-rtcp-5axis-xyzbc-trt-sim-plan/app run build`,构建成功,输出 `gmoccapy_static_build=ok`
15. 检查构建产物大小和文件数,确认 `dist` 目录大小约 `21M`,共 `826` 个文件。
16. 在远端检查 `/var/www/codexcnc-8092``/etc/nginx/sites-available``/etc/nginx/sites-enabled`,确认站点目录和 Nginx 配置目录存在。
17. 在远端执行 `sudo nginx -T` 过滤检查,确认 `codexcnc.com` 配置中包含 `listen 8092 ssl http2``root /var/www/codexcnc-8092`
18. 在本地将 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist` 打包为临时压缩包 `/tmp/codexcnc-dist-20260707103349.tgz`
19. 使用 `scp` 将临时压缩包上传到远端 `/tmp/codexcnc-dist.tgz`
20. 在远端创建 `/var/www/codexcnc-8092.new`,解压最新静态文件到该目录。
21. 在远端设置新目录属主为 `www-data:www-data`,目录权限为 `755`,文件权限为 `644`
22. 在远端备份旧站点目录到 `/var/backups/codexcnc-8092/site-时间戳`,并将旧目录移动到 `/var/www/codexcnc-8092.previous`
23. 将新目录移动为正式站点目录 `/var/www/codexcnc-8092`
24. 执行 `sudo nginx -t`,确认 Nginx 配置语法正确。
25. 执行 `sudo systemctl reload nginx`,完成 Nginx 热重载。
26. 删除远端临时压缩包 `/tmp/codexcnc-dist.tgz`
27. 验证远端正式站点目录大小约 `21M`,文件数为 `826`
28. 再次执行 `curl -I https://codexcnc.com:8092/`,确认首页返回 `HTTP/2 200``last-modified` 已更新为本次发布时间。
29. 执行 `curl -I https://codexcnc.com:8092/src/main.js`,确认主脚本返回 `HTTP/2 200``content-type``text/javascript`
30. 执行 `curl -vkI https://codexcnc.com/``curl --http1.1 -I https://codexcnc.com/`,确认公网访问标准 `443` 端口仍在响应阶段被重置。
31. 在远端本机执行 `curl -k --http1.1 -I https://127.0.0.1/``curl -k --http1.1 -I https://127.0.0.1:8092/``curl -k -I --resolve codexcnc.com:443:127.0.0.1 https://codexcnc.com/`,确认 Nginx 本机 443 和 8092 都能返回 `200`;因此公网 443 问题更像外部链路或安全策略问题,不是站点文件问题。
32. 检查远端 `ss``lsof`,确认 `443` 确实由 Nginx 监听。
33. 为匹配用户指定的 `8092` 发布目标,备份 `/etc/nginx/sites-available/codexcnc-8092``/var/backups/codexcnc-8092/codexcnc-8092.nginx.时间戳.before-http-redirect.conf`
34. 使用 `sed` 将远端 HTTP `80` 端口跳转从 `https://$host$request_uri` 调整为 `https://$host:8092$request_uri`
35. 再次执行 `sudo nginx -t``sudo systemctl reload nginx`,确认配置正确并生效。
36. 执行 `curl -I http://codexcnc.com/`,确认 HTTP 入口返回 `301 Moved Permanently`,跳转目标为 `https://codexcnc.com:8092/`
37. 执行 `curl -I https://codexcnc.com:8092/wasm-port/build/wasm/core/linuxcnc_interp.wasm`,确认 WASM 资源返回 `HTTP/2 200``content-type``application/wasm`
38. 再次检查 `8092` 证书,确认仍为 Let's Encrypt 签发给 `codexcnc.com` 的有效证书。
39. 使用 Playwright 打开 `https://codexcnc.com:8092/`,确认响应状态为 `200`,页面标题为 `Web XYZBC TRT RTCP Simulation`,当前 URL 为 `https://codexcnc.com:8092/`
40. Playwright 检查 `#app` 文本长度为 `2144`,页面按钮数量为 `73`,未捕获 console error 或 pageerror。
41. 执行 `git status --short`,确认追加日志前工作区干净。
42. 使用 `apply_patch` 将本次 HTTPS 发布、验证和配置调整过程追加到 `gptlog-process/gpdlog.md`
### 结论
已完成网站最新版本的 HTTPS 发布,正式访问地址为 `https://codexcnc.com:8092/`。该地址使用 Let's Encrypt 为 `codexcnc.com` 签发的有效证书,首页、主脚本和 WASM 资源均返回 `200`Playwright 浏览器加载验证通过且无前端错误。HTTP 地址 `http://codexcnc.com/` 已调整为自动跳转到 `https://codexcnc.com:8092/`。标准 `https://codexcnc.com/` 的 443 公网访问仍出现响应阶段连接重置,但远端本机 443 服务正常;本次按用户指定的 8092 端口完成发布。
## 2026-07-07 11:07 EDT
### 提问
用户要求参考 `/home/mes123456/cnc_wams/work/working8-暂停按钮` 中的文档,严格深入解决 `/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan` 暂停按钮没有引用 WASM 新暂停程序、按钮不能真正暂停的问题。
### 执行过程
1. 读取 `work/working8-暂停按钮` 的 README、开发步骤、验收证据确认目标语义是 LinuxCNC task/interpreter 与 motion 双层暂停:`EMC_TASK_PLAN_PAUSE/RESUME``motion.paused``interpResumeState``taskPaused``motionPaused` 必须由 WASM 状态驱动。
2. 搜索并检查目标项目的 `app/src/state/store.js``app/src/runtime/linuxcnc-task-hal-runtime.js``app/src/state/linuxcnc-task-policy.js``app/src/ui/axis-shell.js`,确认当前源码已经能加载 task-hal WASM也有 `motionPaused` 映射,但暂停路径存在前端 `taskHalPauseLock` 强制伪造 paused、停止 status loop 后只读一次 WASM 状态的风险。
3. 校验 `wasm-port` 底层:`verify_task_hal_wasm.mjs` 已覆盖 `pause_freezes_motion_queue``resume_restores_interp_resume_state``step_returns_to_paused`,并确认 `app/dist/wasm-port/build/wasm/task-hal/linuxcnc_task_hal.wasm``wasm-port/build/wasm/task-hal/linuxcnc_task_hal.wasm` 哈希一致。
4. 修改 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/src/state/store.js`:暂停命令不再用 `preserveMachine` 伪造成功状态,必须等待 WASM 返回 `motionPaused/taskPaused/interpState=paused` 后才进入 pausedResume 同样不再用前端状态覆盖 WASM 返回状态。
5. 修改 `applyTaskHalStatusPatch`:状态判定以 WASM status 的 `ui.motionPaused``motionStatus.motion.paused``task.taskPaused``interpState` 为准;去掉 pauseLock 对 paused 状态本身的伪造,只保留显示层锁定。
6. 修改暂停后的 status loop暂停期间保留低频/单步状态监控,`shouldContinueTaskHalStatusLoop` 在真实 paused 状态下继续监控,使 Web 侧持续观察 WASM 是否仍处于暂停,而不是停掉轮询造成假冻结。
7. 修复浏览器 trace 暂停后位置跳动问题:暂停确认后保留 `taskHalPauseLock` 仅用于显示坐标、sample、motionIndex、tcpPose、rtcpFrame、dro 的冻结;同时在暂停态阻止异步 kinematics refresh 覆盖显示位置。该锁不再作为 paused 状态来源paused 状态仍必须来自 WASM。
8. 运行调试脚本分析失败 trace确认旧问题表现为 `axisPose/dro``runtimeTcp/uiExecution.tcp/canvasToolhead` 在 pause hold 阶段变化;补充 tcpPose/rtcpFrame/dro 锁定后重新验证通过。
9. 重新构建 `app/dist`,确保静态页面引用更新后的 `app/src/state/store.js` 与当前 WASM artifact。
### 验证结果
- `node wasm-port/tests/wasm/node/verify_task_hal_wasm.mjs` 通过,输出包含:`pause_freezes_motion_queue=ok``resume_restores_interp_resume_state=ok``step_returns_to_paused=ok`
- `node tests/node/verify_run_feedback_loop.mjs` 通过,输出包含:`pause_uses_motion_paused_gate=ok``pause_freezes_task_hal_status_loop=ok`
- `npm --prefix app run build` 通过,输出:`gmoccapy_static_build=ok`
- `APP_URL_PATH=/web-rtcp-5axis-xyzbc-trt-sim-plan/app/dist/index.html node tools/trace-pause-position-json.mjs` 最终通过,输出:`pause_position_status=passed-position-frozen``position_changed=false``changed_fields=`trace 路径:`/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/pause-position-traces/pause-position-20260707T150717Z/trace.json`
- `RUN_STABLE_BEFORE_FIRST_PAUSE_MS=3000 node tools/verify-estop-power-home-run-pause-50ms.mjs` 在新构建后通过,输出:`verification_status=passed`,截图目录:`/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/screenshots/estop-power-home-run-pause-50ms-20260707T145810Z`manifest`/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan/working/screenshots/estop-power-home-run-pause-50ms-20260707T145810Z/manifest.json`
- `npm --prefix app run smoke:node` 通过,输出:`xyzbc_trt_web_app_smoke=ok`
### 结论
已严格修复暂停按钮的真实 WASM 接入问题:暂停/恢复现在由 task-hal WASM 状态确认Web 不再仅靠前端锁伪造暂停;暂停期间继续监控 WASM paused 状态,同时冻结 UI/Three.js 显示坐标、TCP、DRO、sample最终浏览器 trace 和 50ms 长流程均证明暂停后位置不再变化。
## 2026-07-07 16:31 EDT
### 提问
用户要求分析 `/home/mes123456/cnc_wams/linuxcnc` 源程序中 AXIS 主界面“急停、上电、Home、执行、暂停、单步执行”的详细调用链条和状态机制包括 C++ task、motion、状态记录方式、按钮先决条件并基于分析结果指导完善 `/home/mes123456/cnc_wams/wasm-port` 的 LinuxCNC WASM 和 `/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan` 程序,最后将分析结果写入单独文档。
### 执行过程
1. 读取根目录 `AGENTS.md`,确认本次执行结束后必须将完整过程日志以中文追加到 `gptlog-process/gpdlog.md`,日志时间戳精确到分钟。
2. 读取 `wasm-port/AGENTS.md`,确认 `wasm-port` 的规则是LinuxCNC 源码为语义源,移植代码应优先复用 vendored LinuxCNC不应扩展项目自写 G-code、planner、task、motion 语义。
3. 使用 `ls``find``rg --files` 梳理仓库结构,确认主要分析对象包括 `linuxcnc/bin/axis``linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc``linuxcnc/src/emc/task/emctaskmain.cc``linuxcnc/src/emc/task/emctask.cc``linuxcnc/src/emc/task/taskintf.cc``linuxcnc/src/emc/motion/command.c``linuxcnc/src/emc/motion/homing.c``linuxcnc/src/emc/nml_intf/emc_nml.hh`
4.`linuxcnc/bin/axis` 中定位 AXIS 按钮入口:`estop_clicked()``onoff_clicked()``task_run()``task_pause()``task_step()``task_resume()``task_pauseresume()``home_all_joints()``home_joint()`,确认 AXIS UI 侧通过 `s.poll()` 读取状态,通过 `c.state()``c.mode()``c.home()``c.auto()` 发送命令。
5.`emcmodule.cc` 中定位 Python C 扩展映射:`mode()` 创建 `EMC_TASK_SET_MODE``state()` 创建 `EMC_TASK_SET_STATE``home()` 创建 `EMC_JOINT_HOME``emcauto()` 创建 `EMC_TASK_PLAN_RUN/PAUSE/RESUME/STEP`,确认 AXIS 按钮不是直接操作 motion而是写 NML 命令。
6.`emctaskmain.cc` 中分析 `emcTaskPlan()` 的状态门禁,确认 task 根据 `task.state``task.mode``task.interpState` 分层允许或拒绝命令;重点检查 OFF/ESTOP/ESTOP_RESET、ON/MANUAL、ON/AUTO/IDLE、ON/AUTO/READING、ON/AUTO/PAUSED、ON/AUTO/WAITING、ON/MDI 的命令接收分支。
7.`emctaskmain.cc` 中分析 `EMC_TASK_PLAN_RUN``EMC_TASK_PLAN_PAUSE``EMC_TASK_PLAN_RESUME``EMC_TASK_PLAN_STEP` 的处理Run 设置 `interpState=READING` 和清 pause/stepPause 调 `emcTrajPause()` 并设置 `interpState=PAUSED/task_paused=1`Resume 调 `emcTrajResume()` 并恢复 `interpResumeState`Step 在 IDLE/READING/PAUSED/WAITING 下有不同分支。
8.`emctask.cc` 中分析 `emcTaskSetState()``OFF` 分支 abort、disable、IO abort、清 volatile home`ON` 分支调用 `emcTrajEnable()``ESTOP_RESET` 分支释放 IO estop 并 abort/synch`ESTOP` 分支 motion abort、IO estop on、traj disable、task abort、清 volatile home。
9.`emctask.cc` 中分析 `determineState()``emcTaskUpdate()`,确认 GUI 可见 `task.state` 不是简单按钮写入,而是由 IO estop 和 `motion.traj.enabled` 推导IO estop 为真即 `ESTOP`,非 estop 且 motion disabled 为 `ESTOP_RESET`motion enabled 为 `ON`
10.`taskintf.cc` 中分析 task 到 motion 的桥接:`emcTrajEnable()` 下发 `EMCMOT_ENABLE``emcTrajDisable()` 下发 `EMCMOT_DISABLE``emcTrajPause()` 下发 `EMCMOT_PAUSE``emcTrajResume()` 下发 `EMCMOT_RESUME``emcTrajStep()` 下发 `EMCMOT_STEP``emcJointHome()` 下发 `EMCMOT_JOINT_HOME`
11.`motion/command.c` 中分析 motion 侧命令:`EMCMOT_ENABLE` 检查 HAL enable 输入并设置 enabling`EMCMOT_DISABLE` 清 enabling`EMCMOT_PAUSE``tpPause()` 并设置 `emcmotStatus->paused=1``EMCMOT_RESUME` 清 stepping 并恢复 planner`EMCMOT_STEP` 设置 `idForStep/stepping``EMCMOT_JOINT_HOME` 要求 FREE 模式、未 inhibit、未 homing、motion enabled。
12.`motion/homing.c` 中分析 homing 状态机,确认 `H[joint].homing``H[joint].homed``H[joint].home_state``joint_in_sequence` 通过 HAL pin 输出;完成时 `homed=1/home_state=HOME_IDLE`,异常时清所有 homed 和 free_tp。
13.`taskintf.cc` 中分析 `emcMotionUpdate()``emcTrajUpdate()` 状态发布,确认 motion 共享状态被复制到 `emcStatus->motion`,包括 enabled、paused、queue、id、current_vel 等;在 `emcmodule.cc` 中确认 Python `linuxcnc.stat()` 暴露 `task_state/task_mode/interp_state/task_paused/enabled/paused/single_stepping` 等字段。
14. 检查 `wasm-port` 的 README、`docs/source-reuse-map.md``docs/axis-style-simulation-implementation.md` 和当前 `runtime/core/linuxcnc_wrap/linuxcnc_task_hal_wasm.cpp``runtime/sdk/src/linuxcnc-task-hal.js`,确认当前 task/HAL WASM 仍是 `phase4_minimal`,有手写 JSON 解析、手写 task 状态和从 G-code 行派生 motion 的过渡实现。
15. 检查 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/src/main.js``app/src/state/linuxcnc-task-policy.js``app/src/runtime/linuxcnc-task-hal-runtime.js`、既有 working 文档,确认 Web 项目已有 AXIS 按钮策略、task/HAL runtime、浏览器和 Node 验证基础,但后续仍应继续把 Web store 状态收敛为 taskHalStatus 的投影。
16. 使用 `apply_patch` 新增独立文档 `项目分析/AXIS主控制按钮调用链与WASM完善指南.md`,用中文写入 929 行分析内容覆盖总体架构、AXIS UI 入口、Python C 扩展到 NML、task 调度、急停、上电、Home、Run、Pause、Step、Resume、按钮前置条件、wasm-port 完善路线、Web 项目完善路线和验证矩阵。
17. 使用 `wc -l``sed` 抽查新文档,确认文件已生成且开头内容正确。
18. 使用 `git status --short` 检查工作区,确认本次新增了 `项目分析/AXIS主控制按钮调用链与WASM完善指南.md`,同时工作区存在多处用户或既有改动,本次未回退这些无关改动。
19. 使用 `date '+%Y-%m-%d %H:%M %Z'` 获取日志时间戳为 `2026-07-07 16:31 EDT`
20. 使用 `apply_patch` 将本次完整执行过程追加到 `gptlog-process/gpdlog.md`
### 结论
已完成 LinuxCNC AXIS 主控制按钮的源码级调用链和状态机制分析,并写入独立文档 `项目分析/AXIS主控制按钮调用链与WASM完善指南.md`。文档明确指出 LinuxCNC 的真实链路是 AXIS Python/Tk 按钮通过 Python C 扩展发送 `EMC_TASK_*`/`EMC_JOINT_*` NML 命令,由 C++ task 层门禁和状态合成,再通过 `taskintf.cc` 下发到 C motion 层处理,最终由 `emcStatus` 暴露给 GUI。文档同时给出 `wasm-port``web-rtcp-5axis-xyzbc-trt-sim-plan` 的完善路线:减少手写 CNC 语义,把按钮和 Web 状态收敛到 LinuxCNC task/motion 状态机与 WASM status 投影。
## 2026-07-07 16:39
### 提问
用户要求分析 `/home/mes123456/cnc_wams/linuxcnc` 源程序中 AXIS 主界面的“急停、上电、Home、执行、暂停、单步执行”的详细调用链条和状态机制包括 C++ task、motion、状态记录方式、每个按钮的先决条件并指导完善 `/home/mes123456/cnc_wams/wasm-port` 的 LinuxCNC WASM 和 `/home/mes123456/cnc_wams/web-rtcp-5axis-xyzbc-trt-sim-plan` 程序,分析结果写入单独文档。
### 执行过程
1. 读取并遵守根目录 `AGENTS.md`,确认每次 GPT/Codex 执行完毕后必须将完整执行过程以中文追加到 `gptlog-process/gpdlog.md`,时间戳精确到分钟。
2. 读取 `wasm-port/AGENTS.md`,确认 `wasm-port` 的约束是:上游 `linuxcnc/` 只作为只读语义源,移植代码应放在 `wasm-port/`,应优先复用 LinuxCNC 源码,不应扩展项目自写 CNC 语义。
3. 使用 `rg --files``find``ls` 梳理仓库结构,确认 AXIS UI 文件、task/motion 源码、`wasm-port``web-rtcp-5axis-xyzbc-trt-sim-plan` 的相关目录。
4.`linuxcnc/share/axis/tcl/axis.tcl` 中定位主菜单和工具栏按钮:急停 `estop_clicked`、电源 `onoff_clicked`、运行 `task_run`、单步 `task_step`、暂停 `task_pause`、恢复 `task_resume`、工具栏暂停/恢复合并 `task_pauseresume`、Home `home_joint`
5. 分析 `axis.tcl update_state()`,确认按钮 UI 门禁:电源按钮要求 `task_state != STATE_ESTOP`Run 要求 `STATE_ON && INTERP_IDLE`Home/Unhome/Zero 要求 `STATE_ON && INTERP_IDLE`Step 要求 `STATE_ON && taskfile != ""`Pause 菜单要求 `STATE_ON && (INTERP_READING || INTERP_WAITING)`Resume 菜单要求 `STATE_ON && INTERP_PAUSED`;工具栏 Pause/Resume 要求 `STATE_ON && interp_state != IDLE`
6.`linuxcnc/src/emc/usr_intf/axis/scripts/axis.py` 中定位按钮回调,确认急停通过 `s.poll()` 后按 `STATE_ESTOP` 切换 `STATE_ESTOP_RESET/STATE_ESTOP`;电源按钮仅在当前 `STATE_ESTOP_RESET` 时发送 `STATE_ON`,否则发送 `STATE_OFF`Home 通过 `manual_ok()``ensure_mode(MODE_MANUAL)``go_home()``c.home()`Run 通过 `ensure_mode(MODE_AUTO)``c.auto(AUTO_RUN, line)`Pause/Resume/Step 通过 `c.auto(AUTO_PAUSE/AUTO_RESUME/AUTO_STEP)`
7.`axis.py AxisCanon.update()` 中分析状态回写,确认 AXIS 通过 `linuxcnc.stat().poll()` 读取 `task_mode/task_state/task_paused/taskfile/interp_state/paused/motion_mode/homed` 等字段,然后写入 Tk 变量,触发 Tcl trace 刷新 UI。
8.`linuxcnc/src/emc/usr_intf/axis/extensions/emcmodule.cc` 中分析 Python C++ 扩展,确认 `state()` 创建 `EMC_TASK_SET_STATE``mode()` 创建 `EMC_TASK_SET_MODE``home()` 创建 `EMC_JOINT_HOME``emcauto()` 创建 `EMC_TASK_PLAN_RUN/PAUSE/RESUME/STEP``stat` 对象暴露 `task_state/task_mode/interp_state/task_paused/paused/homed`
9.`linuxcnc/src/emc/nml_intf/emc.hh` 中确认核心枚举:`EMC_TASK_STATE` 包含 `ESTOP/ESTOP_RESET/OFF/ON``EMC_TASK_MODE` 包含 `MANUAL/AUTO/MDI``EMC_TASK_INTERP` 包含 `IDLE/READING/PAUSED/WAITING``EMC_TASK_EXEC` 包含 `DONE/WAITING_FOR_MOTION/...`
10.`linuxcnc/src/emc/task/emctaskmain.cc` 中分析 task 主循环和文件头说明,确认 LinuxCNC task 每周期调用 `emcTaskPlan()``emcTaskExecute()``emcTaskPlan()` 按 task state、task mode、interp state 对新命令做门禁;`emcTaskExecute()``interp_list` 取命令、检查 pre/post condition、下发命令并更新 `execState`
11.`emctaskmain.cc` 中分析 `EMC_TASK_PLAN_RUN``EMC_TASK_PLAN_PAUSE``EMC_TASK_PLAN_RESUME``EMC_TASK_PLAN_STEP` 分支,确认 Run 设置 `interpState=READING/task_paused=0`Pause 保存 `interpResumeState` 并设置 `interpState=PAUSED/task_paused=1`Resume 恢复 `interpResumeState` 并清 `task_paused/single_stepping/stepping`Step 在 IDLE/READING/PAUSED/WAITING 下分别启动运行、设置单步或触发 motion step。
12.`linuxcnc/src/emc/task/emctask.cc` 中分析 `emcTaskAbort()``emcTaskSetMode()``emcTaskSetState()``determineMode()``determineState()``emcTaskUpdate()`,确认 task 可见状态由下级状态合成:`io.aux.estop` 为真即 `ESTOP`,非急停但 motion disabled 为 `ESTOP_RESET`motion enabled 为 `ON`task mode 由 motion traj mode 和 `mdiOrAuto` 推导。
13.`linuxcnc/src/emc/task/taskintf.cc` 中分析 task 到 motion 的桥接,确认 `emcJointHome()``EMCMOT_JOINT_HOME``emcTrajEnable()``EMCMOT_ENABLE``emcTrajDisable()``EMCMOT_DISABLE``emcTrajPause()``EMCMOT_PAUSE``emcTrajResume()``EMCMOT_RESUME``emcTrajStep()``EMCMOT_STEP`
14.`linuxcnc/src/emc/motion/command.c` 中分析 motion 命令处理,确认 `EMCMOT_PAUSE``tpPause()` 并设置 `emcmotStatus->paused=1``EMCMOT_RESUME` 清 stepping 并 `tpResume()``EMCMOT_STEP` 在 paused 状态下设置 `idForStep/stepping` 并临时恢复,`EMCMOT_JOINT_HOME` 要求 FREE 模式、无 homing inhibit、无其他 homing、motion enable。
15.`linuxcnc/src/emc/motion/control.c` 中分析单步 motion 侧完成条件,确认当 `emcmotStatus->stepping` 且 motion id 改变时motion 自动 `tpPause()`、清 stepping、保持 paused。
16.`linuxcnc/src/emc/motion/homing.c``homing.h` 中分析 Home 状态机,确认 `do_home_joint()` 触发单 joint 或 home all`do_homing()` 每个 servo period 推进状态,完成后 `homed=1/home_state=HOME_IDLE``get_allhomed()` 遍历所有 active joint 的 homed 状态。
17. 检查 `wasm-port/runtime/core/linuxcnc_wrap/linuxcnc_task_hal_wasm.cpp``linuxcnc_motion_runtime.c``wasm-port/runtime/sdk/src/linuxcnc-task-hal.js`,确认当前 WASM task/HAL runtime 仍是 `phase4_minimal`:使用字符串和 JSON 手写状态,`SET_STATE` 直接写 stateHome 简化为零位运动Step 只有部分 `single_stepping`,尚未完整移植 LinuxCNC 的 `determineState()``homing.c` 和 task/motion 双层单步语义。
18. 检查 `web-rtcp-5axis-xyzbc-trt-sim-plan/app/src/state/linuxcnc-task-policy.js``app/src/state/store.js``app/src/runtime/linuxcnc-task-hal-runtime.js` 和既有文档,确认 Web 端已有按钮策略和 task/HAL runtime 接入,但仍应继续把 `taskState/interpState/allHomed/taskPaused/motionPaused` 收敛为 WASM status 投影,减少 reducer 乐观赋值。
19. 使用 `apply_patch` 新增独立中文文档 `项目分析/AXIS主界面按钮调用链与WASM完善建议.md`,共 723 行覆盖总体调用链、状态模型、UI 门禁、急停、上电、Home、Run、Pause/Resume、Step、每个按钮先决条件与状态影响总表、`wasm-port` 完善建议、`web-rtcp` 完善建议和推荐验收序列。
20. 使用 `wc -l``sed` 抽查新文档,确认文件已生成且开头内容正确。
21. 使用 `git status --short` 检查工作区,确认本次新增 `项目分析/AXIS主界面按钮调用链与WASM完善建议.md`;同时工作区存在多处用户或既有改动,本次未回退、未覆盖这些无关改动。
22. 使用 `date '+%Y-%m-%d %H:%M'` 获取日志时间戳为 `2026-07-07 16:39`
23. 使用 `apply_patch` 将本次完整执行过程追加到 `gptlog-process/gpdlog.md`
### 结论
已完成 LinuxCNC AXIS 主界面“急停、上电、Home、执行、暂停、单步执行”的源码级调用链、状态机制、按钮先决条件和状态记录方式分析并写入独立文档 `项目分析/AXIS主界面按钮调用链与WASM完善建议.md`。文档明确指出真实机制是 AXIS Tcl/Python 发 NML 命令C++ task 层按状态/模式/解释器状态门禁并合成 statusC motion/homing 层维护运动、暂停、单步和回零状态GUI 通过 `linuxcnc.stat()` 轮询 status 后刷新按钮。文档同时给出 `wasm-port``web-rtcp-5axis-xyzbc-trt-sim-plan` 的后续完善路线:把 task state、Home、Pause/Resume/Step 从 JS 或最小 wrapper 逐步下沉到 LinuxCNC-derived WASM runtime并让 Web store 只作为 status 投影。
## 2026-07-07 16:43 EDT
### 提问
用户要求按 `/home/mes123456/cnc_wams/项目分析/AXIS主控制按钮调用链与WASM完善指南.md` 文档,在 `/home/mes123456/cnc_wams/work/working9-执行过程按钮` 创建如下文档:`01-项目功能内容``02-项目程序开发详细步骤``03-推进台账``04-任务矩阵``05-验收证据``06-决策记录`,并另加 README 索引,便于后续按任务矩阵继续推进。
### 执行过程
1. 读取根目录 `AGENTS.md` 规则,确认本次执行结束后必须将完整执行过程以中文追加到 `gptlog-process/gpdlog.md`,时间戳精确到分钟。
2. 使用 `rg --files``find``sed` 检查仓库中与本任务相关的文件,确认目标指南存在于 `项目分析/AXIS主控制按钮调用链与WASM完善指南.md`,目标目录 `work/working9-执行过程按钮` 已存在,且根目录下还有 `gptlog-process/gpdlog.md`
3. 读取 `项目分析/AXIS主控制按钮调用链与WASM完善指南.md` 第一段内容,确认文档说明 AXIS 按钮真实链路为 `AXIS Python/Tk -> linuxcnc.command() -> EMC_TASK/EMC_JOINT NML -> task -> taskintf -> motion -> emcStatus -> linuxcnc.stat().poll()`
4. 继续读取指南后半部分,重点确认 Run 执行按钮的 LinuxCNC 语义:`task_run()``ensure_mode(MODE_AUTO)`,再发送 `AUTO_RUN`task 侧通过 `EMC_TASK_PLAN_RUN` 设置 `interpState=READING`、清暂停/单步状态,并要求 machine ON、AUTO、已 homed 或 `no_force_homing`、程序已打开。
5. 同时确认 Run 不是孤立按钮必须与急停、上电、Home、Pause、Resume、Step 的状态链一起规划ESTOP/RESET、Power ON、Manual Home、AUTO Run、Reading/Waiting、Pause/Resume/Step、Done/Error/Abort。
6. 读取 `work/working8-暂停按钮/README.md``01-项目功能内容.md``02-项目程序开发详细步骤.md``04-任务矩阵.md`,参考既有 working8 文档组织方式和编号方式。
7. 使用 `git status --short` 检查工作区,确认已有多处与本任务无关的修改和未跟踪内容;本次未回退、未修改这些无关变更,只在 `work/working9-执行过程按钮` 新建文档,并追加根日志。
8. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/README.md`,写入主题、文档索引、当前推进入口和当前结论,明确 working9 以“执行 Run”按钮为主线但覆盖完整执行控制链。
9. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/01-项目功能内容.md`写入项目目标、LinuxCNC 对标依据、必须覆盖范围、暂不覆盖范围、当前代码基线、主要差距、完成定义和风险提醒。
10. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/02-项目程序开发详细步骤.md`,按步骤列出 Run 状态门禁表、program open/canonical plan 边界、`EMC_TASK_PLAN_RUN` 状态转换、Web store status 投影、Home 过程态、Pause/Resume/Step 回归、构建回归命令和记录关闭流程。
11. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/03-推进台账.md`,建立每轮记录模板,并追加第 0 轮“文档初始化”记录,说明本轮目标、做了什么、改了哪些文件、验证了什么、结果证据和下一步。
12. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/04-任务矩阵.md`,定义状态含义,并建立 `W9-000``W9-011` 任务矩阵覆盖文档初始化、Run 门禁、Web policy、program open、Run 状态转换、motion 推进字段、Web status 投影、Home 前置、Pause/Resume/Step 回归、浏览器验收、构建同步和证据闭环。
13. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/05-验收证据.md`,写入证据记录规则、第 0 轮证据和后续任务待补证据清单。
14. 使用 `apply_patch` 新增 `work/working9-执行过程按钮/06-决策记录.md`记录三条初始决策working9 以 Run 为主线但覆盖完整控制链Web `runState` 只能作为 status 投影G-code 到 motion 不再扩展 JS 正则 fallback。
15. 使用 `find` 核对 `work/working9-执行过程按钮` 下已生成 7 个 Markdown 文件README、01、02、03、04、05、06。
16. 使用 `wc -l 'work/working9-执行过程按钮/'*.md` 统计新文档共 606 行,确认每个文档均非空。
17. 使用 `sed` 抽查 README 和任务矩阵,确认索引、入口、`W9-000``W9-001` 等内容存在。
18. 使用 `date '+%Y-%m-%d %H:%M %Z'` 获取当时检查时间为 `2026-07-07 16:42 EDT`
19. 发现初始写入台账和验收证据时误填为 `16:56 EDT`,随即使用 `apply_patch``03-推进台账.md``05-验收证据.md` 的时间戳修正为 `2026-07-07 16:42 EDT`
20. 再次使用 `find``rg``git status --short -- 'work/working9-执行过程按钮' 'gptlog-process/gpdlog.md'` 核对结果,确认 working9 新目录为未跟踪新增,根日志为已修改,且时间戳已无 `16:56` 残留。
21. 使用 `apply_patch` 将本次创建 working9 文档体系的完整中文执行过程追加到 `gptlog-process/gpdlog.md`
22. 最后核对 `tail` 时发现日志文件末尾已有较早的 `2026-07-07 16:39` 记录,刚追加的 `16:42` 记录位于该记录之前,不符合“追加至文件末尾”的要求。
23. 使用 `rg -n '^## 2026-07-07 16:(31|39|42)' gptlog-process/gpdlog.md` 定位相关日志标题行,确认 `16:42` 记录位于 `16:39` 记录之前。
24. 使用 `apply_patch` 删除中间位置的 `16:42` 记录,并以当前时间 `2026-07-07 16:43 EDT` 将本次完整记录重新追加到文件末尾。
### 结论
已在 `work/working9-执行过程按钮` 创建完整推进文档体系:`README.md``01-项目功能内容.md``02-项目程序开发详细步骤.md``03-推进台账.md``04-任务矩阵.md``05-验收证据.md``06-决策记录.md`。文档明确 working9 后续以 AXIS 风格 Run 执行按钮为主线并把急停、上电、Home、Pause、Resume、Step 纳入 Run 的前置条件和执行过程状态链。任务矩阵已从 `W9-001` 开始安排后续可执行任务,便于继续按矩阵推进。

View File

@@ -1781,7 +1781,13 @@ export function createSimulationStore(seed = {}) {
{ type: "EMC_TASK_PLAN_PAUSE" },
], {
operatorMessage: "task/HAL program paused",
preserveMachine: pausedMachine,
}).then((status) => {
if (isTaskHalStatusPaused(status)) {
startTaskHalStatusLoop({
batchSize: 1,
operatorMessage: "task/HAL paused status monitor running",
});
}
}).catch(() => {});
break;
}
@@ -1858,7 +1864,6 @@ export function createSimulationStore(seed = {}) {
{ type: "EMC_TASK_PLAN_RESUME" },
], {
operatorMessage: "task/HAL program resumed",
preserveMachine: resumedMachine,
}).then((status) => {
if (shouldContinueTaskHalStatusLoop(state, status)) {
startTaskHalStatusLoop({
@@ -2274,6 +2279,9 @@ export function createSimulationStore(seed = {}) {
};
const refreshAsyncKinematicsFrame = async ({ operatorMessage = state.operatorMessage } = {}) => {
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) {
return state.rtcpFrame;
}
if (state.desiredFrameSourceMode !== "source-derived-kinematics-wasm") {
return state.rtcpFrame;
}
@@ -2296,6 +2304,14 @@ export function createSimulationStore(seed = {}) {
if (state.asyncFrameRefreshSequence !== sequence) {
return state.rtcpFrame;
}
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) {
state = {
...state,
asyncFrameRefreshPending: false,
};
notify();
return state.rtcpFrame;
}
const frame = buildRtcpFrame({
axisPose: state.axisPose,
activeLine: state.activeLine,
@@ -2988,6 +3004,7 @@ export function createSimulationStore(seed = {}) {
};
const scheduleAsyncKinematicsRefresh = () => {
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) return null;
if (state.asyncFrameRefreshPending) return null;
if (state.desiredFrameSourceMode !== "source-derived-kinematics-wasm") return null;
if (!state.kinematicsRuntime?.loaded || !isAsyncKinematicsRuntime(state.kinematicsRuntime)) return null;
@@ -3779,43 +3796,44 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
const ui = status?.ui || {};
const task = status?.task || {};
const motion = status?.motionStatus?.motion || {};
const pauseLock = state.taskHalPauseLock?.active === true ? state.taskHalPauseLock : null;
const statusInterpState = normalizeTaskHalInterpState(ui.interpState || task.interpState);
const statusMotionPaused = ui.motionPaused === true || motion.paused === true;
const statusTaskPaused = ui.taskPaused === true || task.taskPaused === true;
const statusPaused = statusInterpState === "paused" || statusMotionPaused || statusTaskPaused;
const rawTaskState = normalizeTaskHalTaskState(ui.taskState || task.state);
const taskState = preserveMachine?.powerOn && rawTaskState === "estop-reset"
? "on"
: rawTaskState;
const taskMode = pauseLock
const taskMode = statusPaused
? programControlModeForMachine(state.machine)
: normalizeLinuxCncTaskMode(preserveMachine?.mode || ui.taskMode || task.mode || state.machine.mode);
const manualPanel = taskMode === "manual"
? (preserveMachine?.manualPanel || state.machine.manualPanel || "manual")
: null;
const interpState = pauseLock
const interpState = statusPaused
? "paused"
: Object.hasOwn(preserveMachine || {}, "interpState")
? normalizeTaskHalInterpState(preserveMachine.interpState)
: normalizeTaskHalInterpState(ui.interpState || task.interpState);
: statusInterpState;
const allHomed = Boolean(preserveMachine?.allHomed ?? state.machine.allHomed);
const activeLine = state.programStartLine + Math.max(Number(ui.activeLine || 1) - 1, 0);
const kinsType = resolveTaskHalKinsType(state, status, activeLine);
const motionPaused = Boolean(pauseLock) ||
ui.motionPaused === true ||
motion.paused === true ||
preserveMachine?.motionPaused === true;
const taskPaused = ui.taskPaused === true || task.taskPaused === true || preserveMachine?.taskPaused === true;
const paused = Boolean(pauseLock) || interpState === "paused" || motionPaused || taskPaused;
const motionPaused = statusMotionPaused || preserveMachine?.motionPaused === true;
const taskPaused = statusTaskPaused || preserveMachine?.taskPaused === true;
const paused = interpState === "paused" || motionPaused || taskPaused;
const interpResumeState = normalizeTaskHalInterpState(
ui.interpResumeState ||
task.interpResumeState ||
(paused ? state.machine.interpResumeState || "reading" : interpState),
);
const axisPose = pauseLock?.axisPose
? clampAxisPoseToProfile(pauseLock.axisPose, state.profile)
: paused
? clampAxisPoseToProfile(state.axisPose, state.profile)
const displayPauseLock = paused && state.taskHalPauseLock?.active === true
? state.taskHalPauseLock
: null;
const axisPose = displayPauseLock?.axisPose
? clampAxisPoseToProfile(displayPauseLock.axisPose, state.profile)
: preserveAxisPose
? clampAxisPoseToProfile({ ...state.axisPose, ...preserveAxisPose }, state.profile)
: resolveTaskHalAxisPose(state, status);
: resolveTaskHalAxisPose(state, status, { paused, runState: state.runState });
const currentVelocity = paused
? 0
: Number.isFinite(ui.currentVelocity)
@@ -3841,9 +3859,9 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
const shouldTrackProgramPlayback = runState === "running"
|| runState === "mdi"
|| (runState === "complete" && (state.runState === "running" || state.runState === "mdi" || state.runState === "complete"));
const taskHalSampleIndex = pauseLock
const taskHalSampleIndex = displayPauseLock
? clampNumber(
pauseLock.sampleIndex,
displayPauseLock.sampleIndex,
0,
Math.max(Number(state.programAxisPreviewPath?.samples?.length || state.programAxisPreviewPath?.sampleCount || 1) - 1, 0),
)
@@ -3857,15 +3875,15 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
const idleRuntimeFeedback = {
...runtimeFeedback,
sampleIndex: taskHalSampleIndex,
motionIndex: pauseLock
? Number(pauseLock.motionIndex || 0)
motionIndex: displayPauseLock
? Number(displayPauseLock.motionIndex || 0)
: Number(state.programExecutionMotionIndex || runtimeFeedback.motionIndex || 0),
...(paused ? {
axisPose,
tcp: {
x: Number(state.tcpPose?.x ?? axisPose.x ?? 0),
y: Number(state.tcpPose?.y ?? axisPose.y ?? 0),
z: Number(state.tcpPose?.z ?? axisPose.z ?? 0),
x: Number(displayPauseLock?.tcpPose?.x ?? state.tcpPose?.x ?? axisPose.x ?? 0),
y: Number(displayPauseLock?.tcpPose?.y ?? state.tcpPose?.y ?? axisPose.y ?? 0),
z: Number(displayPauseLock?.tcpPose?.z ?? state.tcpPose?.z ?? axisPose.z ?? 0),
},
} : {}),
};
@@ -3875,12 +3893,12 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
axisPose,
kinsType,
rtcpState: rtcpStateFromKinsType(kinsType),
motionIndex: pauseLock ? Number(pauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
motionIndex: displayPauseLock ? Number(displayPauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
sampleIndex: taskHalSampleIndex,
runtimeFeedback: {
...runtimeFeedback,
sampleIndex: taskHalSampleIndex,
motionIndex: pauseLock ? Number(pauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
motionIndex: displayPauseLock ? Number(displayPauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
axisPose,
currentVelocityMmPerMin: paused ? 0 : runtimeFeedback.currentVelocityMmPerMin,
requestedVelocityMmPerMin: paused ? 0 : runtimeFeedback.requestedVelocityMmPerMin,
@@ -3900,6 +3918,8 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
? createProgramUiExecution({
...state,
axisPose,
tcpPose: displayPauseLock?.tcpPose || state.tcpPose,
rtcpFrame: displayPauseLock?.rtcpFrame || state.rtcpFrame,
kinsType: kinsType || state.kinsType,
programExecutionSampleIndex: taskHalSampleIndex,
programRuntimeFeedback: idleRuntimeFeedback,
@@ -3909,7 +3929,7 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
const loopActive = state.taskHalStatusLoop?.active === true
&& loopSequence !== null
&& Number(state.taskHalStatusLoop.sequence) === Number(loopSequence)
&& (runState === "running" || runState === "mdi");
&& (runState === "running" || runState === "mdi" || runState === "paused");
const nextTickCount = loopActive ? Number(state.taskHalStatusLoop.tickCount || 0) + 1 : Number(state.taskHalStatusLoop?.tickCount || 0);
const feedbackHistory = [taskHalPlaybackPatch.programRuntimeFeedback, ...(state.programRuntimeFeedbackHistory || [])].slice(0, 100);
const lineStatus = runState === "complete"
@@ -3921,10 +3941,15 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
return {
taskHalStatus: status,
taskHalExecutionPending: false,
taskHalPauseLock: pauseLock ? { ...pauseLock, lastStatusAt: new Date().toISOString() } : state.taskHalPauseLock,
taskHalPauseLock: displayPauseLock ? { ...displayPauseLock, lastStatusAt: new Date().toISOString() } : null,
taskHalFallbackReason: null,
pendingJogCommand: null,
...taskHalPlaybackPatch,
...(displayPauseLock ? {
tcpPose: displayPauseLock.tcpPose,
rtcpFrame: displayPauseLock.rtcpFrame,
dro: displayPauseLock.dro,
} : {}),
programExecutionSourceMode: "linuxcnc-task-motion-hal-wasm",
machine: {
...state.machine,
@@ -3978,9 +4003,26 @@ function shouldContinueTaskHalStatusLoop(state = {}, status = {}) {
const openedProgramLineCount = Number(task.openedSourceLineCount || task.openedLineCount || 1);
const complete = interpState === "idle"
&& Number(task.nextProgramLine || 0) >= openedProgramLineCount;
if (paused) {
return state.runState === "paused" ||
state.machine?.interpState === "paused" ||
state.machine?.taskPaused === true ||
state.machine?.motionPaused === true;
}
return !aborted && !paused && !complete && (interpState === "reading" || taskMode === "mdi");
}
function isTaskHalStatusPaused(status = {}) {
const ui = status?.ui || {};
const task = status?.task || {};
const motion = status?.motionStatus?.motion || {};
return normalizeTaskHalInterpState(ui.interpState || task.interpState) === "paused" ||
ui.motionPaused === true ||
ui.taskPaused === true ||
motion.paused === true ||
task.taskPaused === true;
}
function isTaskHalRunPausedByOperator(state = {}) {
return state.runState === "paused"
|| state.machine?.interpState === "paused"
@@ -4024,6 +4066,8 @@ function createTaskHalPauseLock(state = {}, source = "pause") {
y: Number(state.tcpPose?.y || 0),
z: Number(state.tcpPose?.z || 0),
},
rtcpFrame: state.rtcpFrame,
dro: state.dro,
semanticBoundary: "linuxcnc_axis_task_pauseresume_freezes_task_hal_status_until_resume",
};
}
@@ -4104,7 +4148,7 @@ function programMotionAtOrBeforeLine(state, activeLine) {
return candidate;
}
function resolveTaskHalAxisPose(state, status) {
function resolveTaskHalAxisPose(state, status, { paused = false, runState = state.runState } = {}) {
const ui = status?.ui || {};
const axisPose = ui.axisPose;
if (!axisPose || typeof axisPose !== "object") {
@@ -4127,6 +4171,10 @@ function resolveTaskHalAxisPose(state, status) {
return addAxisDelta(state.axisPose, ui.axisPoseDelta, state.profile);
}
if (!paused && runState !== "running" && runState !== "mdi" && !isJogStatus(status)) {
return state.axisPose;
}
if (!ui.axisPoseFrame && wouldResetNonZeroPoseToLocalZero(state.axisPose, axisPose)) {
return state.axisPose;
}

View File

@@ -1781,7 +1781,13 @@ export function createSimulationStore(seed = {}) {
{ type: "EMC_TASK_PLAN_PAUSE" },
], {
operatorMessage: "task/HAL program paused",
preserveMachine: pausedMachine,
}).then((status) => {
if (isTaskHalStatusPaused(status)) {
startTaskHalStatusLoop({
batchSize: 1,
operatorMessage: "task/HAL paused status monitor running",
});
}
}).catch(() => {});
break;
}
@@ -1858,7 +1864,6 @@ export function createSimulationStore(seed = {}) {
{ type: "EMC_TASK_PLAN_RESUME" },
], {
operatorMessage: "task/HAL program resumed",
preserveMachine: resumedMachine,
}).then((status) => {
if (shouldContinueTaskHalStatusLoop(state, status)) {
startTaskHalStatusLoop({
@@ -2274,6 +2279,9 @@ export function createSimulationStore(seed = {}) {
};
const refreshAsyncKinematicsFrame = async ({ operatorMessage = state.operatorMessage } = {}) => {
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) {
return state.rtcpFrame;
}
if (state.desiredFrameSourceMode !== "source-derived-kinematics-wasm") {
return state.rtcpFrame;
}
@@ -2296,6 +2304,14 @@ export function createSimulationStore(seed = {}) {
if (state.asyncFrameRefreshSequence !== sequence) {
return state.rtcpFrame;
}
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) {
state = {
...state,
asyncFrameRefreshPending: false,
};
notify();
return state.rtcpFrame;
}
const frame = buildRtcpFrame({
axisPose: state.axisPose,
activeLine: state.activeLine,
@@ -2988,6 +3004,7 @@ export function createSimulationStore(seed = {}) {
};
const scheduleAsyncKinematicsRefresh = () => {
if (state.taskHalPauseLock?.active === true || isTaskHalRunPausedByOperator(state)) return null;
if (state.asyncFrameRefreshPending) return null;
if (state.desiredFrameSourceMode !== "source-derived-kinematics-wasm") return null;
if (!state.kinematicsRuntime?.loaded || !isAsyncKinematicsRuntime(state.kinematicsRuntime)) return null;
@@ -3779,43 +3796,44 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
const ui = status?.ui || {};
const task = status?.task || {};
const motion = status?.motionStatus?.motion || {};
const pauseLock = state.taskHalPauseLock?.active === true ? state.taskHalPauseLock : null;
const statusInterpState = normalizeTaskHalInterpState(ui.interpState || task.interpState);
const statusMotionPaused = ui.motionPaused === true || motion.paused === true;
const statusTaskPaused = ui.taskPaused === true || task.taskPaused === true;
const statusPaused = statusInterpState === "paused" || statusMotionPaused || statusTaskPaused;
const rawTaskState = normalizeTaskHalTaskState(ui.taskState || task.state);
const taskState = preserveMachine?.powerOn && rawTaskState === "estop-reset"
? "on"
: rawTaskState;
const taskMode = pauseLock
const taskMode = statusPaused
? programControlModeForMachine(state.machine)
: normalizeLinuxCncTaskMode(preserveMachine?.mode || ui.taskMode || task.mode || state.machine.mode);
const manualPanel = taskMode === "manual"
? (preserveMachine?.manualPanel || state.machine.manualPanel || "manual")
: null;
const interpState = pauseLock
const interpState = statusPaused
? "paused"
: Object.hasOwn(preserveMachine || {}, "interpState")
? normalizeTaskHalInterpState(preserveMachine.interpState)
: normalizeTaskHalInterpState(ui.interpState || task.interpState);
: statusInterpState;
const allHomed = Boolean(preserveMachine?.allHomed ?? state.machine.allHomed);
const activeLine = state.programStartLine + Math.max(Number(ui.activeLine || 1) - 1, 0);
const kinsType = resolveTaskHalKinsType(state, status, activeLine);
const motionPaused = Boolean(pauseLock) ||
ui.motionPaused === true ||
motion.paused === true ||
preserveMachine?.motionPaused === true;
const taskPaused = ui.taskPaused === true || task.taskPaused === true || preserveMachine?.taskPaused === true;
const paused = Boolean(pauseLock) || interpState === "paused" || motionPaused || taskPaused;
const motionPaused = statusMotionPaused || preserveMachine?.motionPaused === true;
const taskPaused = statusTaskPaused || preserveMachine?.taskPaused === true;
const paused = interpState === "paused" || motionPaused || taskPaused;
const interpResumeState = normalizeTaskHalInterpState(
ui.interpResumeState ||
task.interpResumeState ||
(paused ? state.machine.interpResumeState || "reading" : interpState),
);
const axisPose = pauseLock?.axisPose
? clampAxisPoseToProfile(pauseLock.axisPose, state.profile)
: paused
? clampAxisPoseToProfile(state.axisPose, state.profile)
const displayPauseLock = paused && state.taskHalPauseLock?.active === true
? state.taskHalPauseLock
: null;
const axisPose = displayPauseLock?.axisPose
? clampAxisPoseToProfile(displayPauseLock.axisPose, state.profile)
: preserveAxisPose
? clampAxisPoseToProfile({ ...state.axisPose, ...preserveAxisPose }, state.profile)
: resolveTaskHalAxisPose(state, status);
: resolveTaskHalAxisPose(state, status, { paused, runState: state.runState });
const currentVelocity = paused
? 0
: Number.isFinite(ui.currentVelocity)
@@ -3841,9 +3859,9 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
const shouldTrackProgramPlayback = runState === "running"
|| runState === "mdi"
|| (runState === "complete" && (state.runState === "running" || state.runState === "mdi" || state.runState === "complete"));
const taskHalSampleIndex = pauseLock
const taskHalSampleIndex = displayPauseLock
? clampNumber(
pauseLock.sampleIndex,
displayPauseLock.sampleIndex,
0,
Math.max(Number(state.programAxisPreviewPath?.samples?.length || state.programAxisPreviewPath?.sampleCount || 1) - 1, 0),
)
@@ -3857,15 +3875,15 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
const idleRuntimeFeedback = {
...runtimeFeedback,
sampleIndex: taskHalSampleIndex,
motionIndex: pauseLock
? Number(pauseLock.motionIndex || 0)
motionIndex: displayPauseLock
? Number(displayPauseLock.motionIndex || 0)
: Number(state.programExecutionMotionIndex || runtimeFeedback.motionIndex || 0),
...(paused ? {
axisPose,
tcp: {
x: Number(state.tcpPose?.x ?? axisPose.x ?? 0),
y: Number(state.tcpPose?.y ?? axisPose.y ?? 0),
z: Number(state.tcpPose?.z ?? axisPose.z ?? 0),
x: Number(displayPauseLock?.tcpPose?.x ?? state.tcpPose?.x ?? axisPose.x ?? 0),
y: Number(displayPauseLock?.tcpPose?.y ?? state.tcpPose?.y ?? axisPose.y ?? 0),
z: Number(displayPauseLock?.tcpPose?.z ?? state.tcpPose?.z ?? axisPose.z ?? 0),
},
} : {}),
};
@@ -3875,12 +3893,12 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
axisPose,
kinsType,
rtcpState: rtcpStateFromKinsType(kinsType),
motionIndex: pauseLock ? Number(pauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
motionIndex: displayPauseLock ? Number(displayPauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
sampleIndex: taskHalSampleIndex,
runtimeFeedback: {
...runtimeFeedback,
sampleIndex: taskHalSampleIndex,
motionIndex: pauseLock ? Number(pauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
motionIndex: displayPauseLock ? Number(displayPauseLock.motionIndex || 0) : runtimeFeedback.motionIndex,
axisPose,
currentVelocityMmPerMin: paused ? 0 : runtimeFeedback.currentVelocityMmPerMin,
requestedVelocityMmPerMin: paused ? 0 : runtimeFeedback.requestedVelocityMmPerMin,
@@ -3900,6 +3918,8 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
? createProgramUiExecution({
...state,
axisPose,
tcpPose: displayPauseLock?.tcpPose || state.tcpPose,
rtcpFrame: displayPauseLock?.rtcpFrame || state.rtcpFrame,
kinsType: kinsType || state.kinsType,
programExecutionSampleIndex: taskHalSampleIndex,
programRuntimeFeedback: idleRuntimeFeedback,
@@ -3909,7 +3929,7 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
const loopActive = state.taskHalStatusLoop?.active === true
&& loopSequence !== null
&& Number(state.taskHalStatusLoop.sequence) === Number(loopSequence)
&& (runState === "running" || runState === "mdi");
&& (runState === "running" || runState === "mdi" || runState === "paused");
const nextTickCount = loopActive ? Number(state.taskHalStatusLoop.tickCount || 0) + 1 : Number(state.taskHalStatusLoop?.tickCount || 0);
const feedbackHistory = [taskHalPlaybackPatch.programRuntimeFeedback, ...(state.programRuntimeFeedbackHistory || [])].slice(0, 100);
const lineStatus = runState === "complete"
@@ -3921,10 +3941,15 @@ function applyTaskHalStatusPatch(state, status, operatorMessage, {
return {
taskHalStatus: status,
taskHalExecutionPending: false,
taskHalPauseLock: pauseLock ? { ...pauseLock, lastStatusAt: new Date().toISOString() } : state.taskHalPauseLock,
taskHalPauseLock: displayPauseLock ? { ...displayPauseLock, lastStatusAt: new Date().toISOString() } : null,
taskHalFallbackReason: null,
pendingJogCommand: null,
...taskHalPlaybackPatch,
...(displayPauseLock ? {
tcpPose: displayPauseLock.tcpPose,
rtcpFrame: displayPauseLock.rtcpFrame,
dro: displayPauseLock.dro,
} : {}),
programExecutionSourceMode: "linuxcnc-task-motion-hal-wasm",
machine: {
...state.machine,
@@ -3978,9 +4003,26 @@ function shouldContinueTaskHalStatusLoop(state = {}, status = {}) {
const openedProgramLineCount = Number(task.openedSourceLineCount || task.openedLineCount || 1);
const complete = interpState === "idle"
&& Number(task.nextProgramLine || 0) >= openedProgramLineCount;
if (paused) {
return state.runState === "paused" ||
state.machine?.interpState === "paused" ||
state.machine?.taskPaused === true ||
state.machine?.motionPaused === true;
}
return !aborted && !paused && !complete && (interpState === "reading" || taskMode === "mdi");
}
function isTaskHalStatusPaused(status = {}) {
const ui = status?.ui || {};
const task = status?.task || {};
const motion = status?.motionStatus?.motion || {};
return normalizeTaskHalInterpState(ui.interpState || task.interpState) === "paused" ||
ui.motionPaused === true ||
ui.taskPaused === true ||
motion.paused === true ||
task.taskPaused === true;
}
function isTaskHalRunPausedByOperator(state = {}) {
return state.runState === "paused"
|| state.machine?.interpState === "paused"
@@ -4024,6 +4066,8 @@ function createTaskHalPauseLock(state = {}, source = "pause") {
y: Number(state.tcpPose?.y || 0),
z: Number(state.tcpPose?.z || 0),
},
rtcpFrame: state.rtcpFrame,
dro: state.dro,
semanticBoundary: "linuxcnc_axis_task_pauseresume_freezes_task_hal_status_until_resume",
};
}
@@ -4104,7 +4148,7 @@ function programMotionAtOrBeforeLine(state, activeLine) {
return candidate;
}
function resolveTaskHalAxisPose(state, status) {
function resolveTaskHalAxisPose(state, status, { paused = false, runState = state.runState } = {}) {
const ui = status?.ui || {};
const axisPose = ui.axisPose;
if (!axisPose || typeof axisPose !== "object") {
@@ -4127,6 +4171,10 @@ function resolveTaskHalAxisPose(state, status) {
return addAxisDelta(state.axisPose, ui.axisPoseDelta, state.profile);
}
if (!paused && runState !== "running" && runState !== "mdi" && !isJogStatus(status)) {
return state.axisPose;
}
if (!ui.axisPoseFrame && wouldResetNonZeroPoseToLocalZero(state.axisPose, axisPose)) {
return state.axisPose;
}

Some files were not shown because too many files have changed in this diff Show More