对齐 gmoccapy XYZAB 模式互锁

This commit is contained in:
2026-06-26 18:19:49 -04:00
parent 0664ea9bd7
commit 544bfd4b4b
13 changed files with 591 additions and 6 deletions

View File

@@ -218,6 +218,12 @@ function gateMode(status, requestedMode) {
if (status.taskMode === "auto" && status.interpState !== "idle" && targetMode !== "auto") { if (status.taskMode === "auto" && status.interpState !== "idle" && targetMode !== "auto") {
return block(status, "mode blocked: AUTO interpreter is not idle"); return block(status, "mode blocked: AUTO interpreter is not idle");
} }
if (status.taskState !== "on") {
return block(status, `mode blocked: machine must be on before ${targetMode.toUpperCase()}`);
}
if ((targetMode === "mdi" || targetMode === "auto") && !status.allHomed && !status.noForceHoming) {
return block(status, `mode blocked: home machine before ${targetMode.toUpperCase()}`);
}
return allow(status); return allow(status);
} }

View File

@@ -349,14 +349,17 @@ function mdiQuickCommands(state) {
function renderSidebar(element, state, dispatch) { function renderSidebar(element, state, dispatch) {
const tcpKinsType = tcpKinsTypeForProfile(state.profile); const tcpKinsType = tcpKinsTypeForProfile(state.profile);
const tcpDisabled = tcpKinsType ? "" : ` disabled title="${escapeHtml(`${state.profile.id} is ${state.profile.kinematics} reference only`)}"`; const tcpDisabled = tcpKinsType ? "" : ` disabled title="${escapeHtml(`${state.profile.id} is ${state.profile.kinematics} reference only`)}"`;
const autoGate = gateLinuxCncTaskAction(state, { type: "SET_MODE", mode: "auto" });
const manualGate = gateLinuxCncTaskAction(state, { type: "SET_MODE", mode: "manual" });
const mdiGate = gateLinuxCncTaskAction(state, { type: "SET_MODE", mode: "mdi" });
element.innerHTML = ` element.innerHTML = `
${iconButtonHtml({ buttonId: "tbtn_estop", action: "estop", label: "E-STOP", title: "Estop the machine / F1", className: "sidebar-button estop", active: state.machine.estopActive })} ${iconButtonHtml({ buttonId: "tbtn_estop", action: "estop", label: "E-STOP", title: "Estop the machine / F1", className: "sidebar-button estop", active: state.machine.estopActive })}
${iconButtonHtml({ buttonId: "tbtn_on", action: "power", label: "POWER", title: "Turn the machine on/off / F2", className: "sidebar-button power", active: state.machine.powerOn })} ${iconButtonHtml({ buttonId: "tbtn_on", action: "power", label: "POWER", title: "Turn the machine on/off / F2", className: "sidebar-button power", active: state.machine.powerOn })}
${iconButtonHtml({ buttonId: "tbtn_setup", action: "reset", label: "RESET", title: "Reset estop and return machine off", className: "sidebar-button", active: false })} ${iconButtonHtml({ buttonId: "tbtn_setup", action: "reset", label: "RESET", title: "Reset estop and return machine off", className: "sidebar-button", active: false })}
${iconButtonHtml({ buttonId: "rbt_auto", action: "mode-auto", label: "AUTO", title: "Enter auto mode", className: "sidebar-button", active: state.machine.mode === "auto" })} ${iconButtonHtml({ buttonId: "rbt_auto", action: "mode-auto", label: "AUTO", title: autoGate.operatorMessage || "Enter auto mode", className: "sidebar-button", active: state.machine.mode === "auto", gate: autoGate })}
${iconButtonHtml({ buttonId: "rbt_manual", action: "mode-manual", label: "MANUAL", title: "Enter manual mode", className: "sidebar-button", active: state.machine.mode === "manual" })} ${iconButtonHtml({ buttonId: "rbt_manual", action: "mode-manual", label: "MANUAL", title: manualGate.operatorMessage || "Enter manual mode", className: "sidebar-button", active: state.machine.mode === "manual", gate: manualGate })}
${iconButtonHtml({ buttonId: "tbtn_user_tabs", action: "mode-jog", label: "JOG", title: "Manual jog page", className: "sidebar-button", active: state.machine.mode === "manual" })} ${iconButtonHtml({ buttonId: "tbtn_user_tabs", action: "mode-jog", label: "JOG", title: manualGate.operatorMessage || "Manual jog page", className: "sidebar-button", active: state.machine.mode === "manual", gate: manualGate })}
${iconButtonHtml({ buttonId: "rbt_mdi", action: "mode-mdi", label: "MDI", title: "Enter MDI mode", className: "sidebar-button", active: state.machine.mode === "mdi" })} ${iconButtonHtml({ buttonId: "rbt_mdi", action: "mode-mdi", label: "MDI", title: mdiGate.operatorMessage || "Enter MDI mode", className: "sidebar-button", active: state.machine.mode === "mdi", gate: mdiGate })}
<button type="button" class="sidebar-button" data-action="kins-identity" data-active="${state.kinsType === "identity"}">IDENTITY</button> <button type="button" class="sidebar-button" data-action="kins-identity" data-active="${state.kinsType === "identity"}">IDENTITY</button>
<button type="button" class="sidebar-button" data-action="kins-tcp" data-active="${state.kinsType.startsWith("tcp-")}"${tcpDisabled}>TCP</button> <button type="button" class="sidebar-button" data-action="kins-tcp" data-active="${state.kinsType.startsWith("tcp-")}"${tcpDisabled}>TCP</button>
<time>13:30:31<br />20.06.2026</time> <time>13:30:31<br />20.06.2026</time>

View File

@@ -4779,3 +4779,167 @@ Outcome:
- The gmoccapy XYZAB `working4` completion commit was uploaded to the cloud repository. - The gmoccapy XYZAB `working4` completion commit was uploaded to the cloud repository.
- Remote `master` now points to `a81b40447de313117e2a389a394c3b8d80931001`. - Remote `master` now points to `a81b40447de313117e2a389a394c3b8d80931001`.
- A follow-up process-log commit is required because this log entry was created after the main upload commit. - A follow-up process-log commit is required because this log entry was created after the main upload commit.
---
## 2026-06-26 18:15 EDT - working5 LinuxCNC gmoccapy_XYZAB execution-flow alignment
User request:
- Compare `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan` against the five-axis machine execution flow documented in `/home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md`.
- Execute, test, and improve the LinuxCNC-ported CNC simulation so its simulation behavior matches the LinuxCNC source implementation.
- First create working files in `/home/meswork/cnc_wams/work/working5`:
- `01-项目功能内容`
- `02-项目程序开发详细步骤`
- `03-推进台账`
- `04-任务矩阵`
- `05-验收证据`
- `06-决策记录`
- plus README index.
Process:
- Confirmed workspace and root instructions:
- `/home/meswork/cnc_wams`
- `AGENTS.md` requires appending process logs to `web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`.
- Read the LinuxCNC execution-flow reference:
- `/home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md`
- Key baseline: `gmoccapy_XYZAB.ini`, `trivkins coordinates=xyzab`, five joints, `NO_FORCE_HOMING = 0`, NML command/status/error, HAL loopbacks, POSTGUI after `halcomp.ready()`, and gmoccapy button sensitivity rules.
- Inspected project structure and test inventory:
- `app/src/runtime`
- `app/src/state`
- `app/src/ui`
- `app/src/profiles`
- `tests/node`
- `tests/browser`
- Created working5 control documents:
- `/home/meswork/cnc_wams/work/working5/README.md`
- `/home/meswork/cnc_wams/work/working5/01-项目功能内容.md`
- `/home/meswork/cnc_wams/work/working5/02-项目程序开发详细步骤.md`
- `/home/meswork/cnc_wams/work/working5/03-推进台账.md`
- `/home/meswork/cnc_wams/work/working5/04-任务矩阵.md`
- `/home/meswork/cnc_wams/work/working5/05-验收证据.md`
- `/home/meswork/cnc_wams/work/working5/06-决策记录.md`
- Ran initial baseline tests:
- `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run smoke:node`
- Result: passed.
- Key output included:
- `linuxcnc_kinematics_runtime_smoke=ok`
- `linuxcnc_interpreter_runtime_smoke=ok`
- `linuxcnc_ini_runtime_smoke=ok`
- `linuxcnc_task_hal_runtime_smoke=ok`
- `full_linuxcnc_5axis_source_node_smoke=ok`
- `real_linuxcnc_5axis_program_cases_smoke=ok`
- `gmoccapy_xyzab_profile_smoke=ok`
- `gmoccapy_communication_model_smoke=ok`
- `gmoccapy_hal_model_smoke=ok`
- `gmoccapy_xyzab_gates_smoke=ok`
- `linear_unit_conversion_smoke=ok`
- `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run build`
- Result: passed, output `gmoccapy_static_build=ok`.
- `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run smoke`
- Result: passed, output `gmoccapy_shell_smoke=ok` and `gmoccapy_dist_smoke=ok`.
- Reviewed relevant implementation files:
- `app/src/profiles/gmoccapy-xyzab.js`
- `app/src/state/linuxcnc-task-policy.js`
- `app/src/state/store.js`
- `app/src/ui/gmoccapy-shell.js`
- `app/src/runtime/gmoccapy-hal-model.js`
- `app/src/runtime/gmoccapy-communication-model.js`
- `tests/node/verify_gmoccapy_xyzab_gates.mjs`
- `tests/node/verify_run_preconditions.mjs`
- `tests/browser/gmoccapy_shell_smoke.html`
- Compared against LinuxCNC/gmoccapy source:
- `/home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/gmoccapy/gmoccapy.py`
- Observed native behavior:
- `on_hal_status_state_off()` disables `rbt_manual`, `rbt_mdi`, `rbt_auto`, and motion/execution controls.
- `on_hal_status_state_on()` enables Manual and base machine controls.
- With `NO_FORCE_HOMING = 0`, MDI/Auto remain unavailable until all axes are homed.
- Setup-page exit logic restores:
- estop/off: no mode buttons available
- on but not homed: only Manual available
- on and homed or no-force-homing: Manual/MDI/Auto available
- Found a Web behavior gap:
- Existing store/policy blocked `RUN` and `RUN_MDI` in invalid states.
- But mode buttons could still be clicked earlier than gmoccapy allows.
- Initial Web policy allowed mode changes that native gmoccapy would represent as insensitive buttons.
- Implemented fixes:
- In `app/src/state/linuxcnc-task-policy.js`:
- Added `SET_MODE` gate requiring `taskState === "on"` for Manual/Jog/MDI/Auto.
- Added homing gate for Auto/MDI when `NO_FORCE_HOMING = 0`.
- Preserved Auto-running protection that blocks leaving Auto while interpreter is not idle.
- In `app/src/ui/gmoccapy-shell.js`:
- Bound Manual/Jog/Auto/MDI sidebar mode buttons to `gateLinuxCncTaskAction`.
- Buttons now expose disabled state, `data-command-ready`, `aria-disabled`, and tooltip reason from the same task policy used by store dispatch.
- In `tests/node/verify_gmoccapy_xyzab_gates.mjs`:
- Added assertions that down/off state blocks Manual/MDI/Auto mode changes.
- Added assertions that power-on but unhomed state allows Manual but blocks Auto/MDI.
- Added assertions that after homing Auto mode is allowed.
- In `tests/node/verify_run_preconditions.mjs`:
- Updated expected behavior: trying Auto before homing leaves mode in Manual and run gate continues to say switch to Auto first.
- In `tests/browser/gmoccapy_shell_smoke.html`:
- Added DOM assertions for gmoccapy mode button sensitivity:
- initially powered off: Manual/Jog/Auto/MDI disabled
- powered on but unhomed: Manual/Jog enabled, Auto/MDI disabled with homing reason
- homed: Auto/MDI enabled.
- Ran targeted validation:
- `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && node ../tests/node/verify_gmoccapy_xyzab_gates.mjs && node ../tests/node/verify_run_preconditions.mjs && bash ../tests/browser/verify_gmoccapy_shell_browser.sh`
- Result: passed.
- Output:
- `gmoccapy_xyzab_gates_smoke=ok`
- `run_preconditions_ini_profile_smoke=ok`
- `run_preconditions_kinematics_smoke=ok`
- `run_preconditions_machine_file_smoke=ok`
- `gmoccapy_shell_smoke=ok`
- Ran final full regression:
- `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run smoke:node`
- Result: passed.
- `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run build && npm run smoke`
- Result: passed.
- Output:
- `gmoccapy_static_build=ok`
- `gmoccapy_shell_smoke=ok`
- `gmoccapy_dist_smoke=ok`
- Ran quality checks:
- `git diff --check` in `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan`
- Result: passed.
- `git diff --check` in `/home/meswork/cnc_wams`
- Result: passed.
- Updated working5 documentation:
- `03-推进台账.md` now records R1 setup and R2 mode-interlock fix.
- `04-任务矩阵.md` marks current pass complete for baseline profile/task/HAL/interpreter/UI verification.
- `05-验收证据.md` records command evidence and outputs.
- `06-决策记录.md` records why mode gating moved into `SET_MODE` and UI disabled state.
- Started a local static server:
- Command: `python3 -m http.server 4173`
- Directory: `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app`
- Confirmed with:
- `curl -I --max-time 2 http://127.0.0.1:4173/`
- HTTP 200 OK.
- `ss -ltnp 'sport = :4173'`
- Listener: `python3`, PID `24983`.
Files changed:
- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js`
- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html`
- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs`
- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs`
- `/home/meswork/cnc_wams/work/working5/README.md`
- `/home/meswork/cnc_wams/work/working5/01-项目功能内容.md`
- `/home/meswork/cnc_wams/work/working5/02-项目程序开发详细步骤.md`
- `/home/meswork/cnc_wams/work/working5/03-推进台账.md`
- `/home/meswork/cnc_wams/work/working5/04-任务矩阵.md`
- `/home/meswork/cnc_wams/work/working5/05-验收证据.md`
- `/home/meswork/cnc_wams/work/working5/06-决策记录.md`
- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/gptlog-process/gpdlog.md`
Git/worktree notes:
- `git status --short --branch` from `/home/meswork/cnc_wams` shows modified Web simulation files and new `work/working5/`.
- There is an unrelated untracked file already visible outside this task:
- `备忘/备忘3.txt`
- It was not modified.
Outcome:
- Created the requested working5 documentation set and README index.
- Executed and passed Node, build, browser, and targeted gmoccapy gate regressions.
- Fixed a real LinuxCNC/gmoccapy behavior mismatch: mode buttons now follow native `STATE_OFF`, `STATE_ON`, homing, and interpreter-idle sensitivity semantics.
- Local app server is available at `http://127.0.0.1:4173/`.

View File

@@ -303,6 +303,38 @@
if (!doc.querySelector('[data-machine-state="summary"]')?.textContent.includes("power off")) { if (!doc.querySelector('[data-machine-state="summary"]')?.textContent.includes("power off")) {
throw new Error("initial machine state should show power off"); throw new Error("initial machine state should show power off");
} }
const initialModeManualButton = doc.querySelector('[data-action="mode-manual"]');
const initialModeJogButton = doc.querySelector('[data-action="mode-jog"]');
const initialModeAutoButton = doc.querySelector('[data-action="mode-auto"]');
const initialModeMdiButton = doc.querySelector('[data-action="mode-mdi"]');
if (
!initialModeManualButton?.disabled ||
initialModeManualButton.dataset.commandReady !== "false" ||
initialModeManualButton.getAttribute("aria-disabled") !== "true"
) {
throw new Error("MANUAL mode button should be disabled before machine power on");
}
if (
!initialModeJogButton?.disabled ||
initialModeJogButton.dataset.commandReady !== "false" ||
initialModeJogButton.getAttribute("aria-disabled") !== "true"
) {
throw new Error("JOG mode button should be disabled before machine power on");
}
if (
!initialModeAutoButton?.disabled ||
initialModeAutoButton.dataset.commandReady !== "false" ||
initialModeAutoButton.getAttribute("aria-disabled") !== "true"
) {
throw new Error("AUTO mode button should be disabled before machine power on");
}
if (
!initialModeMdiButton?.disabled ||
initialModeMdiButton.dataset.commandReady !== "false" ||
initialModeMdiButton.getAttribute("aria-disabled") !== "true"
) {
throw new Error("MDI mode button should be disabled before machine power on");
}
const initiallyBlockedRunButton = doc.querySelector('[data-action="RUN"]'); const initiallyBlockedRunButton = doc.querySelector('[data-action="RUN"]');
if ( if (
@@ -325,8 +357,44 @@
if (!doc.querySelector('[data-machine-state="summary"]')?.textContent.includes("power on")) { if (!doc.querySelector('[data-machine-state="summary"]')?.textContent.includes("power on")) {
throw new Error("machine state did not render power on"); throw new Error("machine state did not render power on");
} }
const unhomedModeManualButton = doc.querySelector('[data-action="mode-manual"]');
const unhomedModeJogButton = doc.querySelector('[data-action="mode-jog"]');
const unhomedModeAutoButton = doc.querySelector('[data-action="mode-auto"]');
const unhomedModeMdiButton = doc.querySelector('[data-action="mode-mdi"]');
if (
unhomedModeManualButton?.disabled ||
unhomedModeManualButton.dataset.commandReady !== "true" ||
unhomedModeJogButton?.disabled ||
unhomedModeJogButton.dataset.commandReady !== "true"
) {
throw new Error("MANUAL and JOG mode buttons should be enabled after power on");
}
if (
!unhomedModeAutoButton?.disabled ||
unhomedModeAutoButton.dataset.commandReady !== "false" ||
!unhomedModeAutoButton.title.includes("home machine before AUTO")
) {
throw new Error("AUTO mode button should remain disabled until homed");
}
if (
!unhomedModeMdiButton?.disabled ||
unhomedModeMdiButton.dataset.commandReady !== "false" ||
!unhomedModeMdiButton.title.includes("home machine before MDI")
) {
throw new Error("MDI mode button should remain disabled until homed");
}
doc.querySelector('[data-action="HOME"]').click(); doc.querySelector('[data-action="HOME"]').click();
await wait(50); await wait(50);
const homedModeAutoButton = doc.querySelector('[data-action="mode-auto"]');
const homedModeMdiButton = doc.querySelector('[data-action="mode-mdi"]');
if (
homedModeAutoButton?.disabled ||
homedModeAutoButton.dataset.commandReady !== "true" ||
homedModeMdiButton?.disabled ||
homedModeMdiButton.dataset.commandReady !== "true"
) {
throw new Error("AUTO and MDI mode buttons should be enabled after homing");
}
doc.querySelector('[data-action="mode-auto"]').click(); doc.querySelector('[data-action="mode-auto"]').click();
await wait(50); await wait(50);
if (!doc.querySelector('[data-linuxcnc-task-policy="gates"]')?.textContent.includes("auto")) { if (!doc.querySelector('[data-linuxcnc-task-policy="gates"]')?.textContent.includes("auto")) {

View File

@@ -15,18 +15,41 @@ let gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
assert.equal(gate.allowed, false); assert.equal(gate.allowed, false);
assert.equal(gate.operatorMessage, "run blocked: machine must be on"); assert.equal(gate.operatorMessage, "run blocked: machine must be on");
gate = gateLinuxCncTaskAction(store.getState(), { type: "SET_MODE", mode: "manual" });
assert.equal(gate.allowed, false);
assert.equal(gate.operatorMessage, "mode blocked: machine must be on before MANUAL");
gate = gateLinuxCncTaskAction(store.getState(), { type: "SET_MODE", mode: "mdi" });
assert.equal(gate.allowed, false);
assert.equal(gate.operatorMessage, "mode blocked: machine must be on before MDI");
gate = gateLinuxCncTaskAction(store.getState(), { type: "SET_MODE", mode: "auto" });
assert.equal(gate.allowed, false);
assert.equal(gate.operatorMessage, "mode blocked: machine must be on before AUTO");
store.dispatch({ type: "TOGGLE_POWER" }); store.dispatch({ type: "TOGGLE_POWER" });
gate = gateLinuxCncTaskAction(store.getState(), { type: "SET_MODE", mode: "manual" });
assert.equal(gate.allowed, true);
gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" }); gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
assert.equal(gate.allowed, false); assert.equal(gate.allowed, false);
assert.equal(gate.operatorMessage, "run blocked: switch to auto mode first"); assert.equal(gate.operatorMessage, "run blocked: switch to auto mode first");
gate = gateLinuxCncTaskAction(store.getState(), { type: "SET_MODE", mode: "auto" });
assert.equal(gate.allowed, false);
assert.equal(gate.operatorMessage, "mode blocked: home machine before AUTO");
store.dispatch({ type: "SET_MODE", mode: "auto" }); store.dispatch({ type: "SET_MODE", mode: "auto" });
assert.equal(store.getState().machine.mode, "manual");
assert.equal(store.getState().operatorMessage, "mode blocked: home machine before AUTO");
gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" }); gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
assert.equal(gate.allowed, false); assert.equal(gate.allowed, false);
assert.equal(gate.operatorMessage, "run blocked: home machine first"); assert.equal(gate.operatorMessage, "run blocked: switch to auto mode first");
gate = gateLinuxCncTaskAction(store.getState(), { type: "SET_MODE", mode: "mdi" });
assert.equal(gate.allowed, false);
assert.equal(gate.operatorMessage, "mode blocked: home machine before MDI");
store.dispatch({ type: "SET_MODE", mode: "manual" }); store.dispatch({ type: "SET_MODE", mode: "manual" });
store.dispatch({ type: "HOME" }); store.dispatch({ type: "HOME" });
gate = gateLinuxCncTaskAction(store.getState(), { type: "SET_MODE", mode: "auto" });
assert.equal(gate.allowed, true);
store.dispatch({ type: "SET_MODE", mode: "auto" }); store.dispatch({ type: "SET_MODE", mode: "auto" });
gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" }); gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
assert.equal(gate.allowed, false); assert.equal(gate.allowed, false);

View File

@@ -134,10 +134,14 @@ gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
assert.equal(gate.allowed, false); assert.equal(gate.allowed, false);
assert.equal(gate.operatorMessage, "run blocked: switch to auto mode first"); assert.equal(gate.operatorMessage, "run blocked: switch to auto mode first");
gate = gateLinuxCncTaskAction(store.getState(), { type: "SET_MODE", mode: "auto" });
assert.equal(gate.allowed, false);
assert.equal(gate.operatorMessage, "mode blocked: home machine before AUTO");
store.dispatch({ type: "SET_MODE", mode: "auto" }); store.dispatch({ type: "SET_MODE", mode: "auto" });
assert.equal(store.getState().machine.mode, "manual");
gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" }); gate = gateLinuxCncTaskAction(store.getState(), { type: "RUN" });
assert.equal(gate.allowed, false); assert.equal(gate.allowed, false);
assert.equal(gate.operatorMessage, "run blocked: home machine first"); assert.equal(gate.operatorMessage, "run blocked: switch to auto mode first");
store.dispatch({ type: "HOME" }); store.dispatch({ type: "HOME" });
store.dispatch({ type: "SET_MODE", mode: "manual" }); store.dispatch({ type: "SET_MODE", mode: "manual" });

View File

@@ -0,0 +1,56 @@
# 01-项目功能内容
## 目标
以 LinuxCNC `gmoccapy_XYZAB.ini` 五轴仿真配置为行为基准,执行、测试并完善 `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan`。数控系统仿真功能应尽量与 LinuxCNC 源程序行为一致;发现不一致时,在 Web 项目中修复并补充验收证据。
## 对标范围
### 启动与配置
- INI 解析:`[DISPLAY]``[TASK]``[EMCMOT]``[TRAJ]``[KINS]``[HAL]``[RS274NGC]``[PYTHON]` 等关键段。
- 启动顺序:`linuxcncsvr`、RTAPI/HAL、`milltask``halui`、HALFILE、`halcmd start``gmoccapy`、POSTGUI_HALFILE。
- 五轴配置:`COORDINATES = X Y Z A B``JOINTS = 5``trivkins coordinates=xyzab`
### 任务、状态与互锁
- NML 命令、状态、错误通道的 Web 等效模型。
- `STATE_ESTOP``STATE_ESTOP_RESET``STATE_OFF``STATE_ON`
- `MODE_MANUAL``MODE_MDI``MODE_AUTO`
- `INTERP_IDLE`、运行、暂停、停止、abort。
- `NO_FORCE_HOMING = 0` 下的全轴回零互锁。
- 自动运行、MDI、点动、回零、主轴、冷却、倍率、限位、换刀互锁。
### HAL 与仿真 IO
- `core_sim_XYZAB.hal`motmod、五轴位置反馈回环、急停回环、换刀回环、主轴/冷却信号。
- `spindle_sim.hal`:速度斜坡、编码器反馈、`spindle.0.at-speed`
- `simulated_home.hal`X/Y/Z 回零开关模拟。
- `gmoccapy_postgui.hal`GUI HAL pins、主轴反馈条、到速 LED、刀补显示、换刀回环。
- `HALUI = halui` 默认入口。
### G-code 解释与五轴运动
- RS274NGC 解释器行为。
- MDI 命令发送与状态回读。
- 自动运行文件、暂停、继续、单步、停止、从行运行。
- `M6``M61` REMAP 相关换刀语义。
- 五轴轨迹、单位换算、RTCP/运动学边界。
### gmoccapy 界面等效行为
- 周期轮询:`[DISPLAY] CYCLE_TIME = 100` ms。
- 按钮/页面敏感状态互锁。
- DRO、程序进度、主轴反馈、冷却液、工具/刀补、错误消息。
- 外部 HAL 硬按钮和点动 pin 驱动 GUI 后再通过命令通道进入任务层。
## 当前项目入口
- Web 应用:`/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app`
- 核心运行时:`app/src/runtime`
- gmoccapy UI`app/src/ui`
- 状态存储:`app/src/state`
- 机床配置/profile`app/src/profiles`
- Node 验证:`tests/node`
- 浏览器验证:`tests/browser`

View File

@@ -0,0 +1,48 @@
# 02-项目程序开发详细步骤
## 步骤 1建立工作控制文件
- 创建 README、功能内容、开发步骤、推进台账、任务矩阵、验收证据、决策记录。
- 把任务按 LinuxCNC 执行流程拆分为可验收条目。
## 步骤 2读取基准与现有实现
- 读取 `work/working3/gmoccapy_XYZAB_execution_analysis.md`
- 读取 Web 项目 runtime、state、profile、ui 和测试文件。
- 将 LinuxCNC 执行链路映射到 Web 项目模块。
## 步骤 3执行现有测试
-`app` 下运行 `npm run smoke:node`
- 运行构建:`npm run build`
- 运行浏览器冒烟:`npm run smoke`
- 如有失败,先定位是否为环境、测试夹具或功能差异。
## 步骤 4差异分析
按任务矩阵逐项检查:
- INI/HAL/profile 是否覆盖 `gmoccapy_XYZAB.ini` 的五轴语义。
- task/HAL runtime 是否覆盖状态机、互锁、回零、运行、暂停、停止、换刀、主轴、冷却、倍率。
- interpreter/kinematics/session 是否覆盖 LinuxCNC 解释和五轴运行边界。
- UI 是否按 gmoccapy 规则启停按钮并显示状态。
## 步骤 5修改实现
- 优先修复已有测试暴露的问题。
- 对未覆盖但属于 LinuxCNC 基准行为的差异,补测试后修实现。
- 修改范围保持在对应 runtime/state/ui/profile/tests 文件内,避免无关重构。
## 步骤 6回归验收
- 重新运行相关最小测试。
- 运行完整 Node 冒烟测试。
- 对 UI/浏览器改动运行构建与浏览器冒烟。
- 把命令、结果、证据路径写入 `05-验收证据.md`
## 步骤 7更新台账和任务矩阵
- 更新已完成任务状态。
- 写清本轮修改文件、验证命令和下一步。
- 对重要取舍写入 `06-决策记录.md`

View File

@@ -0,0 +1,59 @@
# 03-推进台账
## 2026-06-26 R1启动 working5 对标推进
做了什么:
- 创建 working5 工作文件结构。
- 建立与 `gmoccapy_XYZAB.ini` 执行流程对齐的任务矩阵和验收记录模板。
- 开始读取 LinuxCNC 执行分析和 Web 项目结构。
改了哪些文件:
- `/home/meswork/cnc_wams/work/working5/README.md`
- `/home/meswork/cnc_wams/work/working5/01-项目功能内容.md`
- `/home/meswork/cnc_wams/work/working5/02-项目程序开发详细步骤.md`
- `/home/meswork/cnc_wams/work/working5/03-推进台账.md`
- `/home/meswork/cnc_wams/work/working5/04-任务矩阵.md`
- `/home/meswork/cnc_wams/work/working5/05-验收证据.md`
- `/home/meswork/cnc_wams/work/working5/06-决策记录.md`
验证了什么:
- 待执行现有 Node、构建和浏览器冒烟测试。
下一步是什么:
- 执行 `npm run smoke:node`,根据失败项定位 LinuxCNC 行为差异。
## 2026-06-26 R2基线验证与 gmoccapy 模式互锁修复
做了什么:
- 执行现有 Node、构建、浏览器冒烟测试确认项目基线可运行。
- 对照 `gmoccapy_XYZAB_execution_analysis.md``gmoccapy.py``STATE_OFF``STATE_ON``all_homed` 逻辑,发现 Web 侧允许提前点击 Manual/MDI/Auto 模式按钮store 虽能拦截部分运行动作,但 UI 敏感状态不完全等同 gmoccapy。
- 修改 task policy`SET_MODE` 在机床未上电时禁止 Manual/MDI/Auto/Jog机床上电但未回零时只允许 Manual/Jog禁止 MDI/AutoAuto 运行中仍禁止离开 Auto。
- 修改 gmoccapy Web shell右侧 Manual/Jog/Auto/MDI 模式按钮统一使用 `gateLinuxCncTaskAction`,以 `disabled``data-command-ready``aria-disabled` 和 title 展示互锁原因。
- 补充 Node 和浏览器测试,锁定下电、上电未回零、回零后三个阶段的模式按钮行为。
改了哪些文件:
- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/state/linuxcnc-task-policy.js`
- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app/src/ui/gmoccapy-shell.js`
- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_gmoccapy_xyzab_gates.mjs`
- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs`
- `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html`
- `/home/meswork/cnc_wams/work/working5/03-推进台账.md`
- `/home/meswork/cnc_wams/work/working5/04-任务矩阵.md`
- `/home/meswork/cnc_wams/work/working5/05-验收证据.md`
- `/home/meswork/cnc_wams/work/working5/06-决策记录.md`
验证了什么:
- `node ../tests/node/verify_gmoccapy_xyzab_gates.mjs && node ../tests/node/verify_run_preconditions.mjs && bash ../tests/browser/verify_gmoccapy_shell_browser.sh` 通过。
- `npm run smoke:node` 通过。
- `npm run build && npm run smoke` 通过。
下一步是什么:
- 下一轮可继续按任务矩阵深入核对 Tool/M6/M61 手动换刀对话框、限位 override、optional stop/block delete HAL pin 交叉行为等更细粒度 gmoccapy 行为。

View File

@@ -0,0 +1,16 @@
# 04-任务矩阵
| 任务编号 | 任务 | 状态 | 验收标准 |
| --- | --- | --- | --- |
| W5-001 | 创建 working5 工作控制文件 | 完成 | README 和 01-06 文件存在,内容可指导后续推进 |
| W5-010 | 建立 LinuxCNC 执行流程到 Web 模块映射 | 完成 | 已映射到 profile、task policy、HAL model、communication model、interpreter/runtime、UI shell 和 tests |
| W5-020 | 执行现有 Node 冒烟测试 | 完成 | `npm run smoke:node` 通过 |
| W5-030 | 执行构建测试 | 完成 | `npm run build` 通过 |
| W5-040 | 执行浏览器冒烟测试 | 完成 | `npm run smoke` 通过 |
| W5-100 | 校验 gmoccapy XYZAB profile | 完成 | `verify_gmoccapy_xyzab_profile.mjs`、完整 Node 冒烟通过 |
| W5-110 | 校验 task 状态机和互锁 | 完成 | 修复并验证 Manual/MDI/Auto/Jog 模式按钮在下电、未回零、已回零状态下与 gmoccapy 一致 |
| W5-120 | 校验 HAL runtime | 完成 | `verify_gmoccapy_hal_model.mjs``verify_linuxcnc_task_hal_runtime.mjs`、完整 Node 冒烟通过 |
| W5-130 | 校验 G-code interpreter/runtime | 完成 | 自动运行、MDI、暂停/继续/停止、M6/M61 引用边界测试随完整 Node 冒烟通过 |
| W5-140 | 校验五轴运动和单位换算 | 完成 | kinematics、real LinuxCNC 5-axis cases、linear unit conversion 测试随完整 Node 冒烟通过 |
| W5-150 | 校验 gmoccapy UI 通信和按钮门控 | 完成 | 浏览器 smoke 验证 mode gate、NML/HAL 诊断、POSTGUI 顺序、按钮状态 |
| W5-900 | 更新验收证据和决策记录 | 完成 | 命令、结果、修改原因和后续项已记录 |

View File

@@ -0,0 +1,69 @@
# 05-验收证据
## 证据索引
| 证据编号 | 任务编号 | 类型 | 命令/页面/文件 | 结果 |
| --- | --- | --- | --- | --- |
| EV-W5-001 | W5-001 | 文件 | `/home/meswork/cnc_wams/work/working5` | 已创建 README 和 01-06 工作控制文件 |
| EV-W5-020 | W5-020/W5-100/W5-110/W5-120/W5-130/W5-140 | 命令 | `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run smoke:node` | 通过,输出包含 `gmoccapy_xyzab_gates_smoke=ok``gmoccapy_hal_model_smoke=ok``real_linuxcnc_5axis_program_cases_smoke=ok``linear_unit_conversion_smoke=ok` |
| EV-W5-030 | W5-030 | 命令 | `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run build` | 通过,输出 `gmoccapy_static_build=ok` |
| EV-W5-040 | W5-040/W5-150 | 命令 | `cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app && npm run smoke` | 通过,输出 `gmoccapy_shell_smoke=ok``gmoccapy_dist_smoke=ok` |
| EV-W5-110 | W5-110/W5-150 | 命令 | `node ../tests/node/verify_gmoccapy_xyzab_gates.mjs && node ../tests/node/verify_run_preconditions.mjs && bash ../tests/browser/verify_gmoccapy_shell_browser.sh` | 通过,验证下电禁用 Manual/Jog/MDI/Auto上电未回零只允许 Manual/Jog回零后允许 Auto/MDI |
## 命令记录
### 2026-06-26 R2
```bash
cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app
npm run smoke:node
```
结果:通过。
关键输出摘要:
- `linuxcnc_kinematics_runtime_smoke=ok`
- `linuxcnc_interpreter_runtime_smoke=ok`
- `linuxcnc_task_hal_runtime_smoke=ok`
- `full_linuxcnc_5axis_source_node_smoke=ok`
- `real_linuxcnc_5axis_program_cases_smoke=ok`
- `gmoccapy_xyzab_profile_smoke=ok`
- `gmoccapy_communication_model_smoke=ok`
- `gmoccapy_hal_model_smoke=ok`
- `gmoccapy_xyzab_gates_smoke=ok`
- `linear_unit_conversion_smoke=ok`
```bash
cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app
npm run build
```
结果:通过,输出 `gmoccapy_static_build=ok`
```bash
cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app
npm run smoke
```
结果:通过,输出:
- `gmoccapy_shell_smoke=ok`
- `gmoccapy_dist_smoke=ok`
```bash
cd /home/meswork/cnc_wams/web-rtcp-5axis-sim-plan/app
node ../tests/node/verify_gmoccapy_xyzab_gates.mjs && node ../tests/node/verify_run_preconditions.mjs && bash ../tests/browser/verify_gmoccapy_shell_browser.sh
```
结果:通过,输出:
- `gmoccapy_xyzab_gates_smoke=ok`
- `run_preconditions_ini_profile_smoke=ok`
- `run_preconditions_kinematics_smoke=ok`
- `run_preconditions_machine_file_smoke=ok`
- `gmoccapy_shell_smoke=ok`
## 页面/截图/report/job 记录
本轮未生成截图、PDF、job_id 或 report_id。浏览器验收由 headless Chromium smoke 脚本完成。

View File

@@ -0,0 +1,42 @@
# 06-决策记录
## DR-W5-001以 gmoccapy_XYZAB 执行流程作为本轮验收主线
日期2026-06-26
决策:
- 本轮不只验证图形 RTCP 轨迹,而是按 LinuxCNC 启动、INI/HAL、task/NML、interpreter、gmoccapy UI 通信和互锁全链路推进。
原因:
- 用户指定的基准文件覆盖了 `gmoccapy_XYZAB.ini` 五轴仿真从启动到界面通信的完整执行流程。
- 若只看轨迹显示容易遗漏回零、状态机、换刀、主轴到速、冷却、UI gate 等数控系统仿真关键行为。
影响:
- 任务矩阵按 LinuxCNC 行为拆分。
- 任何实现修改都需要补充对应测试或验收证据。
## DR-W5-002模式按钮门控前移到 `SET_MODE` 和 UI disabled 状态
日期2026-06-26
决策:
- Web 侧 `SET_MODE` 统一按 LinuxCNC/gmoccapy 状态门控。
- `STATE_OFF` 或未上电时Manual/Jog/MDI/Auto 都禁止切换。
- `STATE_ON` 但未全轴回零且 `NO_FORCE_HOMING = 0` 时,只允许 Manual/Jog禁止 MDI/Auto。
- 回零后 MDI/Auto 恢复可用Auto 解释器非 idle 时仍禁止离开 Auto。
原因:
- LinuxCNC `gmoccapy.py``on_hal_status_state_off()` 会禁用 `rbt_manual``rbt_mdi``rbt_auto`
- `on_hal_status_state_on()` 先只启用 `rbt_manual`,退出设置页时也明确区分“未回零只 Manual 可用”和“已回零三种模式可用”。
- 旧 Web 实现主要在 `RUN``RUN_MDI` 时拦截,模式按钮本身仍可点击,不符合 gmoccapy 的按钮敏感状态。
影响:
- `app/src/state/linuxcnc-task-policy.js` 成为模式切换和执行动作的统一互锁入口。
- `app/src/ui/gmoccapy-shell.js` 侧边模式按钮使用同一 gate 输出 disabled、`data-command-ready``aria-disabled`
- Node 和浏览器测试增加下电、未回零、已回零三个阶段的断言。

27
work/working5/README.md Normal file
View File

@@ -0,0 +1,27 @@
# working5 README 索引
本目录用于推进 `/home/meswork/cnc_wams/web-rtcp-5axis-sim-plan` 与 LinuxCNC `gmoccapy_XYZAB.ini` 五轴仿真执行流程的一致性验证、修复和验收。
对标基准:
- `/home/meswork/cnc_wams/work/working3/gmoccapy_XYZAB_execution_analysis.md`
- `/home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/gmoccapy_XYZAB.ini`
- `/home/meswork/cnc_wams/linuxcnc/src/emc/usr_intf/gmoccapy/`
- `/home/meswork/cnc_wams/linuxcnc/configs/sim/gmoccapy/*.hal`
工作文件:
1. [01-项目功能内容.md](01-项目功能内容.md)
2. [02-项目程序开发详细步骤.md](02-项目程序开发详细步骤.md)
3. [03-推进台账.md](03-推进台账.md)
4. [04-任务矩阵.md](04-任务矩阵.md)
5. [05-验收证据.md](05-验收证据.md)
6. [06-决策记录.md](06-决策记录.md)
推进规则:
- 先按 `04-任务矩阵.md` 选择任务,避免重复做同一项。
- 每轮结束更新 `03-推进台账.md`,记录做了什么、改了哪些文件、验证了什么、下一步是什么。
- 所有命令、页面、报告、截图、测试输出摘要写入 `05-验收证据.md`
- 影响架构、兼容性或验收口径的判断写入 `06-决策记录.md`