Add RUN precondition gate for 5-axis simulation
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
"build": "node scripts/build-static.mjs",
|
"build": "node scripts/build-static.mjs",
|
||||||
"dev": "python3 -m http.server 4173",
|
"dev": "python3 -m http.server 4173",
|
||||||
"smoke": "bash ../tests/browser/verify_gmoccapy_shell_browser.sh && bash ../tests/browser/verify_gmoccapy_dist_browser.sh",
|
"smoke": "bash ../tests/browser/verify_gmoccapy_shell_browser.sh && bash ../tests/browser/verify_gmoccapy_dist_browser.sh",
|
||||||
"smoke:node": "node ../tests/node/verify_linuxcnc_kinematics_runtime.mjs && node ../tests/node/verify_linuxcnc_interpreter_runtime.mjs && node ../tests/node/verify_linuxcnc_ini_runtime.mjs && node ../tests/node/verify_linuxcnc_task_hal_runtime.mjs && node ../tests/node/verify_native_task_hal_audit.mjs && node ../tests/node/verify_full_linuxcnc_5axis_source.mjs && node ../tests/node/verify_real_linuxcnc_5axis_program_cases.mjs && node ../tests/node/verify_full_execution_boundary.mjs && node ../tests/node/verify_machine_file_staging.mjs && node ../tests/node/verify_five_axis_session.mjs && node ../tests/node/verify_rtcp_store.mjs && node ../tests/node/verify_profile_boundary.mjs"
|
"smoke:node": "node ../tests/node/verify_linuxcnc_kinematics_runtime.mjs && node ../tests/node/verify_linuxcnc_interpreter_runtime.mjs && node ../tests/node/verify_linuxcnc_ini_runtime.mjs && node ../tests/node/verify_run_preconditions.mjs && node ../tests/node/verify_linuxcnc_task_hal_runtime.mjs && node ../tests/node/verify_native_task_hal_audit.mjs && node ../tests/node/verify_full_linuxcnc_5axis_source.mjs && node ../tests/node/verify_real_linuxcnc_5axis_program_cases.mjs && node ../tests/node/verify_full_execution_boundary.mjs && node ../tests/node/verify_machine_file_staging.mjs && node ../tests/node/verify_five_axis_session.mjs && node ../tests/node/verify_rtcp_store.mjs && node ../tests/node/verify_profile_boundary.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
"devDependencies": {}
|
"devDependencies": {}
|
||||||
|
|||||||
@@ -1064,11 +1064,12 @@ export function createSimulationStore(seed = {}) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (state.taskHalRuntime?.loaded) {
|
if (state.taskHalRuntime?.loaded) {
|
||||||
runTaskHalCommandSequence([
|
const preconditions = validateRunPreconditions(state, { requireTaskHalSession: false });
|
||||||
{ type: "EMC_TASK_SET_STATE", state: "ON" },
|
if (!preconditions.ok) {
|
||||||
{ type: "EMC_TASK_SET_MODE", mode: "AUTO" },
|
setState({ operatorMessage: preconditions.operatorMessage });
|
||||||
{ type: "EMC_TASK_PLAN_RUN", line: Math.max(Number(state.activeLine || 1) - 1, 0) },
|
break;
|
||||||
], { taskCycles: 5, operatorMessage: "task/HAL program run" }).catch(() => {});
|
}
|
||||||
|
runValidatedTaskHalProgramRun().catch(() => {});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const playback = nextProgramRuntimeSamplePlayback(state, 5);
|
const playback = nextProgramRuntimeSamplePlayback(state, 5);
|
||||||
@@ -1569,12 +1570,42 @@ export function createSimulationStore(seed = {}) {
|
|||||||
return session;
|
return session;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const runValidatedTaskHalProgramRun = async () => {
|
||||||
|
const preflight = validateRunPreconditions(state, { requireTaskHalSession: false });
|
||||||
|
if (!preflight.ok) {
|
||||||
|
setState({ operatorMessage: preflight.operatorMessage });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedProgramPath = expectedTaskHalProgramPathForState(state);
|
||||||
|
if (!state.taskHalSession || (expectedProgramPath && state.taskHalSession.programPath !== expectedProgramPath)) {
|
||||||
|
await initializeTaskHalSession({ openProgram: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const ready = validateRunPreconditions(state, { requireTaskHalSession: true });
|
||||||
|
if (!ready.ok) {
|
||||||
|
setState({ operatorMessage: ready.operatorMessage });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return runTaskHalCommandSequence([
|
||||||
|
{ type: "EMC_TASK_SET_STATE", state: "ON" },
|
||||||
|
{ type: "EMC_TASK_SET_MODE", mode: "AUTO" },
|
||||||
|
{ type: "EMC_TASK_PLAN_RUN", line: Math.max(Number(state.activeLine || 1) - Number(state.programStartLine || 1), 0) },
|
||||||
|
], {
|
||||||
|
taskCycles: 5,
|
||||||
|
operatorMessage: `task/HAL program run ${ready.profileId} ${ready.kinematicsModuleId}`,
|
||||||
|
allowFixtureSession: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const runTaskHalCommandSequence = async (commands, {
|
const runTaskHalCommandSequence = async (commands, {
|
||||||
taskCycles = 1,
|
taskCycles = 1,
|
||||||
taskPeriodNs = 10000000,
|
taskPeriodNs = 10000000,
|
||||||
servoPeriodNs = 1000000,
|
servoPeriodNs = 1000000,
|
||||||
operatorMessage = "task/HAL command complete",
|
operatorMessage = "task/HAL command complete",
|
||||||
pendingJogCommand = null,
|
pendingJogCommand = null,
|
||||||
|
allowFixtureSession = true,
|
||||||
} = {}) => {
|
} = {}) => {
|
||||||
if (!state.taskHalRuntime?.loaded) {
|
if (!state.taskHalRuntime?.loaded) {
|
||||||
throw new Error("LinuxCNC task/HAL runtime not attached");
|
throw new Error("LinuxCNC task/HAL runtime not attached");
|
||||||
@@ -1589,9 +1620,12 @@ export function createSimulationStore(seed = {}) {
|
|||||||
try {
|
try {
|
||||||
if (!state.taskHalSession && state.machineFileStaging?.save?.files?.length) {
|
if (!state.taskHalSession && state.machineFileStaging?.save?.files?.length) {
|
||||||
await initializeTaskHalSession({ openProgram: true });
|
await initializeTaskHalSession({ openProgram: true });
|
||||||
} else if (!state.taskHalSession && state.programLines?.length) {
|
} else if (allowFixtureSession && !state.taskHalSession && state.programLines?.length) {
|
||||||
await initializeFixtureTaskHalSessionForState();
|
await initializeFixtureTaskHalSessionForState();
|
||||||
}
|
}
|
||||||
|
if (!state.taskHalSession) {
|
||||||
|
throw new Error("LinuxCNC task/HAL session not initialized");
|
||||||
|
}
|
||||||
for (const command of commands) {
|
for (const command of commands) {
|
||||||
await state.taskHalRuntime.sendCommand(command);
|
await state.taskHalRuntime.sendCommand(command);
|
||||||
}
|
}
|
||||||
@@ -1773,6 +1807,142 @@ function isAsyncKinematicsRuntime(runtime) {
|
|||||||
return runtime?.executionContext === "worker";
|
return runtime?.executionContext === "worker";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateRunPreconditions(state = {}, {
|
||||||
|
requireTaskHalRuntime = true,
|
||||||
|
requireTaskHalSession = true,
|
||||||
|
} = {}) {
|
||||||
|
const profile = state.profile || {};
|
||||||
|
const profileId = profile.id || state.machineProfile || "unknown";
|
||||||
|
const supportedProfiles = new Set(["xyzac-trt", "xyzbc-trt"]);
|
||||||
|
const fail = (operatorMessage, detail = {}) => ({
|
||||||
|
apiName: "web-rtcp-5axis-run-preconditions",
|
||||||
|
ok: false,
|
||||||
|
operatorMessage,
|
||||||
|
profileId,
|
||||||
|
iniPath: profile.iniPath || null,
|
||||||
|
coordinates: normalizeCoordinates(profile.traj?.coordinates),
|
||||||
|
kinematicsModuleId: profile.kinematicsModuleId || state.machineProfile || null,
|
||||||
|
...detail,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!supportedProfiles.has(profileId)) {
|
||||||
|
return fail(`run blocked: unsupported five-axis profile ${profileId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!profile.iniPath || state.iniConfigReadiness?.loaded !== true || state.iniConfigReadiness?.ready !== true) {
|
||||||
|
return fail("run blocked: LinuxCNC INI not loaded");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.linuxCncIniConfig?.path && state.linuxCncIniConfig.path !== profile.iniPath) {
|
||||||
|
return fail("run blocked: machine profile and INI path mismatch", {
|
||||||
|
iniPath: state.linuxCncIniConfig.path,
|
||||||
|
expectedIniPath: profile.iniPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileCoordinates = normalizeCoordinates(profile.traj?.coordinates);
|
||||||
|
const iniCoordinates = normalizeCoordinates(state.iniConfigReadiness?.coordinates || state.linuxCncIniConfig?.traj?.coordinates);
|
||||||
|
if (!profileCoordinates || !iniCoordinates || profileCoordinates !== iniCoordinates) {
|
||||||
|
return fail("run blocked: machine profile and INI coordinates mismatch", {
|
||||||
|
coordinates: iniCoordinates || null,
|
||||||
|
expectedCoordinates: profileCoordinates || null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileKinematicsModuleId = profile.kinematicsModuleId || state.machineProfile || null;
|
||||||
|
const iniKinematicsModuleId = state.linuxCncIniConfig?.kinematicsModuleId || profileKinematicsModuleId;
|
||||||
|
if (!profileKinematicsModuleId || profileKinematicsModuleId !== iniKinematicsModuleId) {
|
||||||
|
return fail("run blocked: machine profile and INI kinematics mismatch", {
|
||||||
|
kinematicsModuleId: profileKinematicsModuleId,
|
||||||
|
expectedKinematicsModuleId: iniKinematicsModuleId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const kinematicsReadiness = state.kinematicsRuntimeReadiness || {};
|
||||||
|
const runtimeKinematicsModuleId = kinematicsReadiness.moduleId || state.kinematicsRuntime?.moduleId || null;
|
||||||
|
if (kinematicsReadiness.loaded !== true || state.kinematicsRuntime?.loaded !== true) {
|
||||||
|
return fail("run blocked: LinuxCNC kinematics runtime not ready", {
|
||||||
|
kinematicsModuleId: profileKinematicsModuleId,
|
||||||
|
runtimeKinematicsModuleId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (runtimeKinematicsModuleId !== profileKinematicsModuleId) {
|
||||||
|
return fail("run blocked: LinuxCNC kinematics module mismatch", {
|
||||||
|
kinematicsModuleId: profileKinematicsModuleId,
|
||||||
|
runtimeKinematicsModuleId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (state.rtcpFrame?.sourceMode !== "source-derived-kinematics-wasm") {
|
||||||
|
return fail("run blocked: LinuxCNC kinematics frame not ready", {
|
||||||
|
frameSourceMode: state.rtcpFrame?.sourceMode || state.frameSourceMode || null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.machineFileStaging?.status !== "staged" || !state.machineFileStaging?.save?.files?.length) {
|
||||||
|
return fail("run blocked: LinuxCNC machine files not staged");
|
||||||
|
}
|
||||||
|
if (!state.machineFileStaging?.selectedGcodeSourceRel) {
|
||||||
|
return fail("run blocked: no machine-file G-code opened for task/HAL session");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requireTaskHalRuntime) {
|
||||||
|
const taskHalReadiness = state.taskHalRuntimeReadiness || {};
|
||||||
|
const taskHalReady = state.taskHalRuntime?.loaded === true
|
||||||
|
&& taskHalReadiness.taskRuntimeReady === true
|
||||||
|
&& taskHalReadiness.motionRuntimeReady === true
|
||||||
|
&& taskHalReadiness.halRuntimeReady === true;
|
||||||
|
if (!taskHalReady) {
|
||||||
|
return fail("run blocked: task/HAL runtime not ready");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedProgramPath = expectedTaskHalProgramPathForState(state);
|
||||||
|
if (requireTaskHalSession) {
|
||||||
|
if (!state.taskHalSession?.programPath || !expectedProgramPath) {
|
||||||
|
return fail("run blocked: no machine-file G-code opened for task/HAL session");
|
||||||
|
}
|
||||||
|
if (state.taskHalSession.programPath !== expectedProgramPath) {
|
||||||
|
return fail("run blocked: task/HAL session program mismatch", {
|
||||||
|
programPath: state.taskHalSession.programPath,
|
||||||
|
expectedProgramPath,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
apiName: "web-rtcp-5axis-run-preconditions",
|
||||||
|
ok: true,
|
||||||
|
operatorMessage: null,
|
||||||
|
profileId,
|
||||||
|
machineType: profile.title || null,
|
||||||
|
iniPath: profile.iniPath,
|
||||||
|
coordinates: profileCoordinates,
|
||||||
|
kinematicsModuleId: profileKinematicsModuleId,
|
||||||
|
selectedGcodeSourceRel: state.machineFileStaging.selectedGcodeSourceRel,
|
||||||
|
programPath: expectedProgramPath,
|
||||||
|
sourceMode: "linuxcnc-task-motion-hal-wasm",
|
||||||
|
semanticBoundary: "linuxcnc_ini_profile_kinematics_task_hal_run_preconditions",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectedTaskHalProgramPathForState(state = {}) {
|
||||||
|
const sourceRel = state.machineFileStaging?.selectedGcodeSourceRel;
|
||||||
|
if (!sourceRel || !state.machineFileStaging?.plan || !state.machineFileStaging?.save) return null;
|
||||||
|
try {
|
||||||
|
return selectMachineFileProgram(
|
||||||
|
state.machineFileStaging.plan,
|
||||||
|
state.machineFileStaging.save,
|
||||||
|
sourceRel,
|
||||||
|
).wasmProgramPath || null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCoordinates(value) {
|
||||||
|
return String(value || "").replace(/[^A-Za-z]/g, "").toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
function applyTaskHalStatusPatch(state, status, operatorMessage) {
|
function applyTaskHalStatusPatch(state, status, operatorMessage) {
|
||||||
const ui = status?.ui || {};
|
const ui = status?.ui || {};
|
||||||
const task = status?.task || {};
|
const task = status?.task || {};
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import { fileURLToPath } from "node:url";
|
|||||||
import { dirname, resolve } from "node:path";
|
import { dirname, resolve } from "node:path";
|
||||||
|
|
||||||
import { createLinuxCncTaskHalSdk } from "../../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js";
|
import { createLinuxCncTaskHalSdk } from "../../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js";
|
||||||
|
import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
|
||||||
|
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
|
||||||
|
import { parseLinuxCncIni } from "../../app/src/runtime/linuxcnc-ini-runtime.js";
|
||||||
import { wrapTaskHalSdk } from "../../app/src/runtime/linuxcnc-task-hal-runtime.js";
|
import { wrapTaskHalSdk } from "../../app/src/runtime/linuxcnc-task-hal-runtime.js";
|
||||||
import { createSimulationStore } from "../../app/src/state/store.js";
|
import { createSimulationStore } from "../../app/src/state/store.js";
|
||||||
|
|
||||||
@@ -20,21 +23,37 @@ const sdk = await createLinuxCncTaskHalSdk({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const runtime = wrapTaskHalSdk(sdk);
|
const runtime = wrapTaskHalSdk(sdk);
|
||||||
const store = createSimulationStore({
|
const profile = getFiveAxisProfile("xyzac-trt");
|
||||||
programLines: [
|
const iniText = readFileSync(
|
||||||
"G0 X1 Y2 Z-3 A10 C20",
|
resolve(rootDir, "wasm-port/vendor/linuxcnc", profile.iniPath),
|
||||||
"G1 X2 Y3 Z-4 A11 C21",
|
"utf8",
|
||||||
"M428",
|
);
|
||||||
"G1 X3 Y4 Z-5 A12 C22",
|
const iniConfig = parseLinuxCncIni(iniText, {
|
||||||
],
|
path: profile.iniPath,
|
||||||
activeProgram: "task-hal-store-smoke.ngc",
|
profileId: profile.id,
|
||||||
});
|
});
|
||||||
|
const store = createSimulationStore();
|
||||||
|
|
||||||
|
store.dispatch({ type: "ATTACH_INI_CONFIG", profileId: profile.id, iniConfig });
|
||||||
|
store.dispatch({
|
||||||
|
type: "ATTACH_KINEMATICS_RUNTIME",
|
||||||
|
runtime: await createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" }),
|
||||||
|
});
|
||||||
store.dispatch({ type: "ATTACH_TASK_HAL_RUNTIME", runtime });
|
store.dispatch({ type: "ATTACH_TASK_HAL_RUNTIME", runtime });
|
||||||
assert.equal(store.getState().taskHalRuntimeReadiness.taskRuntimeReady, true);
|
assert.equal(store.getState().taskHalRuntimeReadiness.taskRuntimeReady, true);
|
||||||
assert.equal(store.getState().taskHalRuntimeReadiness.motionRuntimeReady, true);
|
assert.equal(store.getState().taskHalRuntimeReadiness.motionRuntimeReady, true);
|
||||||
assert.equal(store.getState().taskHalRuntimeReadiness.halRuntimeReady, true);
|
assert.equal(store.getState().taskHalRuntimeReadiness.halRuntimeReady, true);
|
||||||
|
|
||||||
|
await store.stageMachineFiles();
|
||||||
|
store.dispatch({
|
||||||
|
type: "LOAD_LINUXCNC_GCODE_SOURCE",
|
||||||
|
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins_test_1.ngc",
|
||||||
|
});
|
||||||
|
await waitForState(store, (state) => (
|
||||||
|
state.machineFileStaging.selectedGcodeSourceRel?.endsWith("xyzac_switchkins_test_1.ngc") &&
|
||||||
|
state.taskHalSession?.programPath?.endsWith("xyzac_switchkins_test_1.ngc")
|
||||||
|
));
|
||||||
|
|
||||||
store.dispatch({ type: "TOGGLE_POWER" });
|
store.dispatch({ type: "TOGGLE_POWER" });
|
||||||
await waitForTaskHal(store);
|
await waitForTaskHal(store);
|
||||||
store.dispatch({ type: "HOME" });
|
store.dispatch({ type: "HOME" });
|
||||||
@@ -47,11 +66,14 @@ let state = store.getState();
|
|||||||
assert.equal(state.taskHalStatus.summary.taskRuntimeReady, true);
|
assert.equal(state.taskHalStatus.summary.taskRuntimeReady, true);
|
||||||
assert.equal(state.taskHalStatus.summary.halSyncReady, true);
|
assert.equal(state.taskHalStatus.summary.halSyncReady, true);
|
||||||
assert.equal(state.taskHalStatus.ui.activeLine >= 1, true);
|
assert.equal(state.taskHalStatus.ui.activeLine >= 1, true);
|
||||||
assert.equal(state.axisPose.x >= 1, true);
|
|
||||||
assert.equal(state.programExecutionSourceMode, "linuxcnc-task-motion-hal-wasm");
|
assert.equal(state.programExecutionSourceMode, "linuxcnc-task-motion-hal-wasm");
|
||||||
assert.equal(state.fullExecutionBoundary.nativeTaskReady, true);
|
assert.equal(state.fullExecutionBoundary.nativeTaskReady, true);
|
||||||
assert.equal(state.fullExecutionBoundary.nativeHalSyncReady, true);
|
assert.equal(state.fullExecutionBoundary.nativeHalSyncReady, true);
|
||||||
|
|
||||||
|
await store.initializeTaskHalSession({ openProgram: true });
|
||||||
|
store.dispatch({ type: "TOGGLE_POWER" });
|
||||||
|
await waitForTaskHal(store);
|
||||||
|
store.dispatch({ type: "HOME" });
|
||||||
store.dispatch({ type: "SET_MODE", mode: "mdi" });
|
store.dispatch({ type: "SET_MODE", mode: "mdi" });
|
||||||
await waitForTaskHal(store);
|
await waitForTaskHal(store);
|
||||||
store.dispatch({ type: "RUN_MDI", command: "M428" });
|
store.dispatch({ type: "RUN_MDI", command: "M428" });
|
||||||
@@ -111,6 +133,15 @@ async function waitForTaskHal(store) {
|
|||||||
throw new Error("task/HAL store command did not settle");
|
throw new Error("task/HAL store command did not settle");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function waitForState(store, predicate) {
|
||||||
|
for (let attempt = 0; attempt < 60; attempt += 1) {
|
||||||
|
const state = store.getState();
|
||||||
|
if (predicate(state)) return state;
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
|
}
|
||||||
|
throw new Error("store state condition did not settle");
|
||||||
|
}
|
||||||
|
|
||||||
function assertNear(actual, expected, message) {
|
function assertNear(actual, expected, message) {
|
||||||
assert.equal(Math.abs(Number(actual) - Number(expected)) < 1e-9, true, `${message}: ${actual} !== ${expected}`);
|
assert.equal(Math.abs(Number(actual) - Number(expected)) < 1e-9, true, `${message}: ${actual} !== ${expected}`);
|
||||||
}
|
}
|
||||||
|
|||||||
133
web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
Normal file
133
web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { readFile } from "node:fs/promises";
|
||||||
|
|
||||||
|
import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
|
||||||
|
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
|
||||||
|
import {
|
||||||
|
applyIniConfigToProfile,
|
||||||
|
parseLinuxCncIni,
|
||||||
|
} from "../../app/src/runtime/linuxcnc-ini-runtime.js";
|
||||||
|
import { stageProfileMachineFiles } from "../../app/src/runtime/linuxcnc-machine-file-staging.js";
|
||||||
|
import { buildTaskHalSessionFromMachineFiles } from "../../app/src/runtime/linuxcnc-task-hal-runtime.js";
|
||||||
|
import {
|
||||||
|
createSimulationStore,
|
||||||
|
validateRunPreconditions,
|
||||||
|
} from "../../app/src/state/store.js";
|
||||||
|
|
||||||
|
const cases = [
|
||||||
|
{
|
||||||
|
profileId: "xyzac-trt",
|
||||||
|
machineName: "sim-xyzac-trt-kins (switchkins)",
|
||||||
|
coordinates: "XYZAC",
|
||||||
|
kinematicsName: "xyzac-trt-kins",
|
||||||
|
kinematicsModuleId: "xyzac-trt",
|
||||||
|
wasmFile: "linuxcnc_xyzac_trt_kinematics.wasm",
|
||||||
|
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins_test_1.ngc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
profileId: "xyzbc-trt",
|
||||||
|
machineName: "sim-xyzbc-trt-kins (switchkins)",
|
||||||
|
coordinates: "XYZBC",
|
||||||
|
kinematicsName: "xyzbc-trt-kins",
|
||||||
|
kinematicsModuleId: "xyzbc-trt",
|
||||||
|
wasmFile: "linuxcnc_xyzbc_trt_kinematics.wasm",
|
||||||
|
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/boat-xyzbc.ngc",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const item of cases) {
|
||||||
|
const baseProfile = getFiveAxisProfile(item.profileId);
|
||||||
|
const iniText = await readLinuxCncFile(baseProfile.iniPath);
|
||||||
|
const iniConfig = parseLinuxCncIni(iniText, {
|
||||||
|
path: baseProfile.iniPath,
|
||||||
|
profileId: item.profileId,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(iniConfig.profileId, item.profileId);
|
||||||
|
assert.equal(iniConfig.machineName, item.machineName);
|
||||||
|
assert.equal(iniConfig.traj.coordinates, item.coordinates);
|
||||||
|
assert.equal(iniConfig.kinematics.name, item.kinematicsName);
|
||||||
|
assert.equal(iniConfig.kinematicsModuleId, item.kinematicsModuleId);
|
||||||
|
assert.equal(iniConfig.validation.ready, true);
|
||||||
|
|
||||||
|
const profile = applyIniConfigToProfile(baseProfile, iniConfig);
|
||||||
|
assert.equal(profile.traj.coordinates, item.coordinates);
|
||||||
|
assert.equal(profile.kinematicsModuleId, item.kinematicsModuleId);
|
||||||
|
assert.equal(profile.axisLimits[item.coordinates[0]].max > 0, true);
|
||||||
|
assert.equal(profile.jointConfig.length, item.coordinates.length);
|
||||||
|
|
||||||
|
const kinematicsRuntime = await createLinuxCncKinematicsRuntime({ moduleId: item.kinematicsModuleId });
|
||||||
|
assert.equal(kinematicsRuntime.readiness().moduleId, item.kinematicsModuleId);
|
||||||
|
assert.equal(kinematicsRuntime.wasmFile, item.wasmFile);
|
||||||
|
assert.equal(kinematicsRuntime.frameForJoints([10, 20, 30, 5, 15]).moduleId, item.kinematicsModuleId);
|
||||||
|
|
||||||
|
const staged = await stageProfileMachineFiles(profile);
|
||||||
|
const selectedPlan = staged.plan.selectedProgramSourceRel === item.sourceRel
|
||||||
|
? staged.plan
|
||||||
|
: {
|
||||||
|
...staged.plan,
|
||||||
|
wasmProgramPath: staged.save.files.find((file) => file.sourceRel === item.sourceRel)?.wasmPath,
|
||||||
|
selectedProgramSourceRel: item.sourceRel,
|
||||||
|
};
|
||||||
|
const session = buildTaskHalSessionFromMachineFiles({
|
||||||
|
profile,
|
||||||
|
plan: selectedPlan,
|
||||||
|
save: staged.save,
|
||||||
|
selectedProgramRel: item.sourceRel,
|
||||||
|
});
|
||||||
|
assert.equal(session.profileId, item.profileId);
|
||||||
|
assert.equal(session.iniPath.endsWith(`${item.profileId}.ini`), true);
|
||||||
|
assert.equal(session.programSourceRel, item.sourceRel);
|
||||||
|
assert.equal(session.programPath.endsWith(item.sourceRel.split("/").at(-1)), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialStore = createSimulationStore();
|
||||||
|
let check = validateRunPreconditions(initialStore.getState(), {
|
||||||
|
requireTaskHalRuntime: false,
|
||||||
|
requireTaskHalSession: false,
|
||||||
|
});
|
||||||
|
assert.equal(check.ok, false);
|
||||||
|
assert.equal(check.operatorMessage, "run blocked: LinuxCNC INI not loaded");
|
||||||
|
|
||||||
|
const profile = getFiveAxisProfile("xyzac-trt");
|
||||||
|
const iniText = await readLinuxCncFile(profile.iniPath);
|
||||||
|
const iniConfig = parseLinuxCncIni(iniText, {
|
||||||
|
path: profile.iniPath,
|
||||||
|
profileId: profile.id,
|
||||||
|
});
|
||||||
|
const store = createSimulationStore();
|
||||||
|
store.dispatch({ type: "ATTACH_INI_CONFIG", profileId: profile.id, iniConfig });
|
||||||
|
store.dispatch({
|
||||||
|
type: "ATTACH_KINEMATICS_RUNTIME",
|
||||||
|
runtime: await createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" }),
|
||||||
|
});
|
||||||
|
await store.stageMachineFiles();
|
||||||
|
store.dispatch({
|
||||||
|
type: "LOAD_LINUXCNC_GCODE_SOURCE",
|
||||||
|
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins_test_1.ngc",
|
||||||
|
});
|
||||||
|
|
||||||
|
check = validateRunPreconditions(store.getState(), {
|
||||||
|
requireTaskHalRuntime: false,
|
||||||
|
requireTaskHalSession: false,
|
||||||
|
});
|
||||||
|
assert.equal(check.ok, true);
|
||||||
|
assert.equal(check.profileId, "xyzac-trt");
|
||||||
|
assert.equal(check.coordinates, "XYZAC");
|
||||||
|
assert.equal(check.kinematicsModuleId, "xyzac-trt");
|
||||||
|
assert.equal(check.selectedGcodeSourceRel.endsWith("xyzac_switchkins_test_1.ngc"), true);
|
||||||
|
|
||||||
|
check = validateRunPreconditions(store.getState(), {
|
||||||
|
requireTaskHalRuntime: true,
|
||||||
|
requireTaskHalSession: false,
|
||||||
|
});
|
||||||
|
assert.equal(check.ok, false);
|
||||||
|
assert.equal(check.operatorMessage, "run blocked: task/HAL runtime not ready");
|
||||||
|
|
||||||
|
console.log("run_preconditions_ini_profile_smoke=ok");
|
||||||
|
console.log("run_preconditions_kinematics_smoke=ok");
|
||||||
|
console.log("run_preconditions_machine_file_smoke=ok");
|
||||||
|
|
||||||
|
async function readLinuxCncFile(sourceRel) {
|
||||||
|
return readFile(new URL(`../../../wasm-port/vendor/linuxcnc/${sourceRel}`, import.meta.url), "utf8");
|
||||||
|
}
|
||||||
@@ -0,0 +1,570 @@
|
|||||||
|
# 01 RUN 与 G-code 执行原理测试记录
|
||||||
|
|
||||||
|
生成时间:2026-06-22
|
||||||
|
|
||||||
|
## 1. 参考接续文件
|
||||||
|
|
||||||
|
本记录按 `textbak` 中的接续要求核对,不把 UI fixture、自增行号、JS-owned CNC 语义当作真实运行证明。
|
||||||
|
|
||||||
|
关键约束来自:
|
||||||
|
|
||||||
|
```text
|
||||||
|
textbak/text2.txt
|
||||||
|
- 数控/G-code/remap/tool/parameter/planner/kinematics/user-M 语义必须来自 LinuxCNC upstream 或 vendored LinuxCNC C/C++ source。
|
||||||
|
- UI 已优先解析 run_step,fallback 到 canonical event。
|
||||||
|
- 不引入 JS G-code parser 或 JS remap/tool/parameter semantics。
|
||||||
|
|
||||||
|
textbak/text40.txt
|
||||||
|
- LinuxCNC 可加载 kinematics module 的独立 WASM ABI 已完成。
|
||||||
|
- 下一步应转向 Web/M4/M5 simulation UI 接入 createLinuxCncKinematicsSdk() 输出,或推进 remap/planner/browser 集成验证。
|
||||||
|
|
||||||
|
textbak/text41.txt
|
||||||
|
- Web/M4/M5 当前状态曾明确记录为 RUN/STEP/JOG/HOME 仍推进 fixture axis pose。
|
||||||
|
- 目标是 RUN/STEP/JOG/HOME 消费 LinuxCNC kinematics frame contract。
|
||||||
|
- program execution 如果仍是 fixture line playback,必须继续标记为 fixture/interpreter-not-connected。
|
||||||
|
- remap/planner/browser full-process 不得因 kinematics 接入而自动 promotion。
|
||||||
|
```
|
||||||
|
|
||||||
|
当前源码已经比 `text41.txt` 的早期基线多接入了 interpreter、kinematics、task/HAL runtime,但 `RUN` 的连续执行模型仍需要按 task/HAL feedback 口径复核。
|
||||||
|
|
||||||
|
## 2. 当前 G-code 加载过程
|
||||||
|
|
||||||
|
启动入口:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/main.js:17-23
|
||||||
|
```
|
||||||
|
|
||||||
|
页面启动时创建 store、挂载 gmoccapy UI,并异步附加:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. LinuxCNC INI config
|
||||||
|
2. LinuxCNC kinematics runtime
|
||||||
|
3. LinuxCNC interpreter runtime
|
||||||
|
4. LinuxCNC task/HAL runtime
|
||||||
|
5. machine file staging
|
||||||
|
```
|
||||||
|
|
||||||
|
默认程序加载:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/main.js:103-124
|
||||||
|
```
|
||||||
|
|
||||||
|
`ensureDefaultLinuxCncProgramPreview()` 在 interpreter 与 machine files staged 后选择默认 LinuxCNC 5-axis source,然后 dispatch:
|
||||||
|
|
||||||
|
```text
|
||||||
|
LOAD_LINUXCNC_GCODE_SOURCE
|
||||||
|
```
|
||||||
|
|
||||||
|
选择 vendored G-code 后,store 的处理链是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:744-792
|
||||||
|
```
|
||||||
|
|
||||||
|
实际步骤:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 从 machineFileStaging.save.files 中找到 sourceRel 对应的 G-code 文本。
|
||||||
|
2. selectMachineFileProgram(...) 生成该 G-code 对应的 machine-file plan。
|
||||||
|
3. buildLoadedProgram(...) 将文本拆成 programLines,设置 activeProgram、programStartLine=1、activeLine=1。
|
||||||
|
4. 设置 programSource=linuxcnc-vendored-5axis-gcode。
|
||||||
|
5. 如果 task/HAL runtime 已加载,initializeTaskHalSession({ openProgram: true })。
|
||||||
|
6. 如果 interpreter runtime 已加载,dispatch RUN_INTERPRETER_PROGRAM。
|
||||||
|
```
|
||||||
|
|
||||||
|
本地 operator 打开文件时走同一个 `buildLoadedProgram(...)`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:1033-1057
|
||||||
|
```
|
||||||
|
|
||||||
|
G-code 文本显示在 UI:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/ui/gmoccapy-shell.js:193-228
|
||||||
|
```
|
||||||
|
|
||||||
|
这里使用 `state.programLines` 逐行渲染,`state.activeLine` 对应的行加 `.active`,同时显示:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Current line ${state.activeLine}
|
||||||
|
${state.activeLine} / ${programEndLine}
|
||||||
|
```
|
||||||
|
|
||||||
|
所以“能否看到 G-code 程序”和“是否逐行显示当前行”取决于 `programLines` 与 `activeLine` 是否被同一执行反馈链正确更新。
|
||||||
|
|
||||||
|
## 3. interpreter 解释与 preview 过程
|
||||||
|
|
||||||
|
`RUN_INTERPRETER_PROGRAM` 不是机床运行按钮,它是在加载程序后调用 LinuxCNC interpreter runtime,生成 canonical motion / summary / timing 基础数据:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:468-508
|
||||||
|
```
|
||||||
|
|
||||||
|
解释完成后:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:509-555
|
||||||
|
```
|
||||||
|
|
||||||
|
当前实现会:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 保存 programExecution。
|
||||||
|
2. buildTimingForState(...) 构建 programExecutionTiming。
|
||||||
|
3. 设置 programExecutionSourceMode=execution.sourceMode。
|
||||||
|
4. 设置 programExecutionMotionIndex=0、programExecutionSampleIndex=0。
|
||||||
|
5. 根据 firstMotion 更新 activeLine、axisPose、kinsType、rtcpState。
|
||||||
|
6. 创建 firstFeedback,并更新 feed.currentVelocity。
|
||||||
|
```
|
||||||
|
|
||||||
|
需要注意:这里的 `axisPoseFromCanonicalMotion(firstMotion, ...)` 来自 canonical motion 事件,是预览/初始反馈,不等价于运行态 task/HAL 连续反馈。
|
||||||
|
|
||||||
|
## 4. timing 与速度来源
|
||||||
|
|
||||||
|
`buildTimingForState(...)` 的优先级:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:2369-2379
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 `execution.plannerTiming.plannerRuntimeReady === true`,使用 plannerTiming;否则调用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
buildProgramExecutionTiming(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
该 timing fallback 的语义边界明确是估算:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/runtime/execution-timing.js:50-53
|
||||||
|
semanticBoundary: linuxcnc_canonical_motion_timing_estimate_not_planner_queue
|
||||||
|
sourceBasis: LinuxCNC canonical motion events plus INI/profile velocity limits and feed overrides
|
||||||
|
```
|
||||||
|
|
||||||
|
fallback timing 的速度计算逻辑:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/runtime/execution-timing.js:75-123
|
||||||
|
```
|
||||||
|
|
||||||
|
它按 canonical motion 段计算:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. rapid 使用 maxLinearVelocity * rapidOverride。
|
||||||
|
2. feed 使用 feedRate * feedOverride。
|
||||||
|
3. linearSeconds = linearDistance / linearVelocity。
|
||||||
|
4. angularSeconds = angularDistance / angularVelocity。
|
||||||
|
5. durationSeconds = max(linearSeconds, angularSeconds)。
|
||||||
|
```
|
||||||
|
|
||||||
|
这不是 LinuxCNC task/motion planner queue 的真实 run_step 推进。除非 `plannerTiming` 已 ready,否则“实际时间”和“实际进给速度”只能说是 canonical-based estimate。
|
||||||
|
|
||||||
|
## 5. RUN 按钮当前执行原理
|
||||||
|
|
||||||
|
`RUN` action 入口:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:1059-1100
|
||||||
|
```
|
||||||
|
|
||||||
|
分两条路径。
|
||||||
|
|
||||||
|
### 5.1 task/HAL runtime loaded 路径
|
||||||
|
|
||||||
|
如果 `state.taskHalRuntime?.loaded` 为 true,`RUN` 执行:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:1066-1072
|
||||||
|
```
|
||||||
|
|
||||||
|
发送命令:
|
||||||
|
|
||||||
|
```text
|
||||||
|
EMC_TASK_SET_STATE ON
|
||||||
|
EMC_TASK_SET_MODE AUTO
|
||||||
|
EMC_TASK_PLAN_RUN line = activeLine - 1
|
||||||
|
```
|
||||||
|
|
||||||
|
然后调用:
|
||||||
|
|
||||||
|
```text
|
||||||
|
runTaskHalCommandSequence(..., { taskCycles: 5 })
|
||||||
|
```
|
||||||
|
|
||||||
|
`runTaskHalCommandSequence` 的实现:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:1572-1604
|
||||||
|
```
|
||||||
|
|
||||||
|
当前真实顺序是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 标记 taskHalExecutionPending=true。
|
||||||
|
2. 如未初始化 task/HAL session,则 initializeTaskHalSession/openProgram。
|
||||||
|
3. 逐个 sendCommand(command)。
|
||||||
|
4. 调用 taskHalRuntime.runCycles({ taskCycles: 5 })。
|
||||||
|
5. 调用 taskHalRuntime.readStatus()。
|
||||||
|
6. dispatch TASK_HAL_STATUS_APPLIED。
|
||||||
|
```
|
||||||
|
|
||||||
|
结论:
|
||||||
|
|
||||||
|
```text
|
||||||
|
当前 RUN 是“发送 task/HAL 命令 -> 主动推进固定 cycles -> 拉取一次 status -> 应用到 UI”。
|
||||||
|
源码中没有看到 task/HAL worker 在程序运行期间主动持续 postMessage status 的订阅/推送通道。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 task/HAL runtime missing fallback
|
||||||
|
|
||||||
|
如果 task/HAL runtime 未加载,`RUN` 走:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:1074-1098
|
||||||
|
```
|
||||||
|
|
||||||
|
即:
|
||||||
|
|
||||||
|
```text
|
||||||
|
nextProgramRuntimeSamplePlayback(state, 5)
|
||||||
|
```
|
||||||
|
|
||||||
|
这个 fallback 按 sampleIndex 或 motionIndex 一次跳 5 步,更新 activeLine / axisPose / feed / runtimeFeedback。
|
||||||
|
|
||||||
|
语义边界在 feedback 中标记为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
linuxcnc-tp-runtime-sample
|
||||||
|
linuxcnc_tp_run_cycle_feedback_without_hardware
|
||||||
|
```
|
||||||
|
|
||||||
|
或在没有 sample 时退回:
|
||||||
|
|
||||||
|
```text
|
||||||
|
linuxcnc-canonical-motion
|
||||||
|
linuxcnc_canonical_motion_feedback_without_tp_sample
|
||||||
|
```
|
||||||
|
|
||||||
|
这条路径不能当作真实 task/HAL 运行过程。
|
||||||
|
|
||||||
|
## 6. task/HAL feedback 如何映射到 UI 状态
|
||||||
|
|
||||||
|
task/HAL runtime 包装:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/runtime/linuxcnc-task-hal-runtime.js:96-114
|
||||||
|
```
|
||||||
|
|
||||||
|
暴露的是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sendCommand(command)
|
||||||
|
runCycles(options)
|
||||||
|
readStatus()
|
||||||
|
```
|
||||||
|
|
||||||
|
worker client:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/runtime/linuxcnc-task-hal-worker-client.js:14-28
|
||||||
|
```
|
||||||
|
|
||||||
|
worker 本体:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/runtime/linuxcnc-task-hal-worker.js:5-17
|
||||||
|
app/src/runtime/linuxcnc-task-hal-worker.js:19-53
|
||||||
|
```
|
||||||
|
|
||||||
|
worker 当前是 request/response 模型:
|
||||||
|
|
||||||
|
```text
|
||||||
|
主线程 postMessage({ id, type, payload })
|
||||||
|
worker 执行 handleMessage(type, payload)
|
||||||
|
worker postMessage({ id, ok, result })
|
||||||
|
```
|
||||||
|
|
||||||
|
没有看到类似:
|
||||||
|
|
||||||
|
```text
|
||||||
|
subscribeStatus
|
||||||
|
onStatus
|
||||||
|
while running post feedback
|
||||||
|
```
|
||||||
|
|
||||||
|
因此,当前 task/HAL feedback 不是“运行时持续推送”,而是 UI/store 调用 `readStatus()` 得到的一次快照。
|
||||||
|
|
||||||
|
状态归一化:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/runtime/linuxcnc-task-hal-runtime.js:155-199
|
||||||
|
```
|
||||||
|
|
||||||
|
`normalizeTaskHalStatus()` 从 motion/HAL pins 生成:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ui.taskState
|
||||||
|
ui.taskMode
|
||||||
|
ui.interpState
|
||||||
|
ui.taskCycle
|
||||||
|
ui.servoCycle
|
||||||
|
ui.motionQueueDepth
|
||||||
|
ui.activeLine
|
||||||
|
ui.switchkinsType
|
||||||
|
ui.axisPose
|
||||||
|
ui.currentVelocity
|
||||||
|
```
|
||||||
|
|
||||||
|
应用到 store:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:1776-1835
|
||||||
|
```
|
||||||
|
|
||||||
|
`applyTaskHalStatusPatch(...)` 将 status 映射为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
activeLine = programStartLine + ui.activeLine - 1
|
||||||
|
axisPose = resolveTaskHalAxisPose(...)
|
||||||
|
kinsType = resolveTaskHalKinsType(...)
|
||||||
|
rtcpState = rtcpStateFromKinsType(kinsType)
|
||||||
|
runState = 根据 interpState / paused / aborted / complete 推导
|
||||||
|
feed.currentVelocity = ui.currentVelocity
|
||||||
|
programRuntimeFeedback = createTaskHalRuntimeFeedback(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
runtime feedback 字段:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:1925-1948
|
||||||
|
```
|
||||||
|
|
||||||
|
包括:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sourceMode=linuxcnc-task-motion-hal-wasm
|
||||||
|
sampleIndex=ui.servoCycle
|
||||||
|
motionIndex=ui.activeLine - 1
|
||||||
|
line=activeLine
|
||||||
|
timeSeconds=ui.taskCycle * 0.01
|
||||||
|
axisPose
|
||||||
|
currentVelocityMmPerMin
|
||||||
|
requestedVelocityMmPerMin
|
||||||
|
distanceToGo
|
||||||
|
queueDepth
|
||||||
|
cycle
|
||||||
|
taskCycle
|
||||||
|
```
|
||||||
|
|
||||||
|
这里的问题是:如果 `readStatus()` 不持续发生,这些字段不会按真实执行时间继续变化。
|
||||||
|
|
||||||
|
## 7. 本地快照观察
|
||||||
|
|
||||||
|
说明:本轮浏览器采集脚本在用户中断前已写出完整 JSON 快照,作为辅助证据。正式结论仍以源码追踪为主。
|
||||||
|
|
||||||
|
辅助证据文件:
|
||||||
|
|
||||||
|
```text
|
||||||
|
web-rtcp-5axis-sim-plan/working_run/run-feedback-evidence/run-feedback-local.json
|
||||||
|
web-rtcp-5axis-sim-plan/working_run/run-feedback-evidence/run-feedback-local.png
|
||||||
|
```
|
||||||
|
|
||||||
|
快照摘要:
|
||||||
|
|
||||||
|
| 快照 | runState | activeLine | servoCycle | velocity | axisPose |
|
||||||
|
| --- | --- | ---: | ---: | ---: | --- |
|
||||||
|
| loaded-default | idle | 1 | 0 | 0 | X0 Y0 Z0 A0 B0 C0 |
|
||||||
|
| after-power | idle | 1 | 10 | 0 | X0 Y0 Z0 A0 B0 C0 |
|
||||||
|
| after-home | idle | 1 | 10 | 0 | X43 Y-32.15 Z-11.306 A0 B0 C0 |
|
||||||
|
| after-auto | idle | 1 | 20 | 0 | X0 Y0 Z0 A0 B0 C0 |
|
||||||
|
| after-run-idle | running | 5 | 70 | 3600 | X0 Y0 Z5 A0 B0 C0 |
|
||||||
|
| run-plus-0.5s-no-command | running | 5 | 70 | 3600 | X0 Y0 Z5 A0 B0 C0 |
|
||||||
|
| run-plus-2.0s-no-command | running | 5 | 70 | 3600 | X0 Y0 Z5 A0 B0 C0 |
|
||||||
|
| run-plus-5.0s-no-command | running | 5 | 70 | 3600 | X0 Y0 Z5 A0 B0 C0 |
|
||||||
|
|
||||||
|
观察结论:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 点击 RUN 后,状态从 line 1 推进到 line 5,servoCycle 从 20 推进到 70。
|
||||||
|
2. 后续 0.5s / 2.0s / 5.0s 没有再次调用命令时,activeLine、servoCycle、axisPose、velocity 均保持不变。
|
||||||
|
3. 这与源码中的 runCycles({ taskCycles: 5 }) + readStatus() 一次相符。
|
||||||
|
4. 这不是连续 task/HAL feedback 推送模型。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. 发现的问题
|
||||||
|
|
||||||
|
### P1. RUN 没有持续消费 task/HAL feedback
|
||||||
|
|
||||||
|
期望:
|
||||||
|
|
||||||
|
```text
|
||||||
|
G-code 程序执行时,task/HAL/motion runtime 持续产生状态;
|
||||||
|
UI/store 持续消费这些状态;
|
||||||
|
当前行、轴值、速度、DTG、程序时间来自同一个 feedback source。
|
||||||
|
```
|
||||||
|
|
||||||
|
当前:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RUN 只主动推进固定 taskCycles=5,然后 readStatus 一次。
|
||||||
|
没有看到 worker 主动推送 status。
|
||||||
|
没有看到 store 中存在运行期间持续 readStatus/readEvents 的执行循环。
|
||||||
|
```
|
||||||
|
|
||||||
|
影响:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RUN 后页面可能停在某个 status snapshot。
|
||||||
|
用户看到 runState=running,但 activeLine、轴值、速度不会随真实时间继续推进。
|
||||||
|
```
|
||||||
|
|
||||||
|
### P1. 轴值存在状态回退/覆盖风险
|
||||||
|
|
||||||
|
快照中可见:
|
||||||
|
|
||||||
|
```text
|
||||||
|
after-home: X43 Y-32.15 Z-11.306
|
||||||
|
after-auto: X0 Y0 Z0
|
||||||
|
```
|
||||||
|
|
||||||
|
源码位置:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js:1874-1901
|
||||||
|
```
|
||||||
|
|
||||||
|
`resolveTaskHalAxisPose()` 对 `ui.axisPoseFrame === "work"` 直接合并 task/HAL axisPose,因此 task/HAL status 中的 0 位姿可覆盖 HOME 后的 fixture/work pose。
|
||||||
|
|
||||||
|
这解释了“现实的轴值都不对”的一类现象:UI 显示值可能来自 task/HAL local/status 快照,而不是实际期望的机床/工件坐标连续反馈。
|
||||||
|
|
||||||
|
### P2. 程序时间和速度可能是 estimate 或单点 status
|
||||||
|
|
||||||
|
fallback timing 明确是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
linuxcnc_canonical_motion_timing_estimate_not_planner_queue
|
||||||
|
```
|
||||||
|
|
||||||
|
task/HAL feedback 中的时间:
|
||||||
|
|
||||||
|
```text
|
||||||
|
timeSeconds = ui.taskCycle * 0.01
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 taskCycle 不推进,程序时间也不推进。快照中 `taskCycle=0`,但 velocity 已为 3600,说明当前 runtime feedback 的时间/速度字段并不构成可靠连续执行轨迹。
|
||||||
|
|
||||||
|
### P2. “逐行执行过程”目前只显示当前 activeLine,不显示执行历史
|
||||||
|
|
||||||
|
UI 已显示:
|
||||||
|
|
||||||
|
```text
|
||||||
|
programLines
|
||||||
|
activeLine
|
||||||
|
gcode-row.active
|
||||||
|
progress
|
||||||
|
```
|
||||||
|
|
||||||
|
但没有看到 execution log / visited line history / per-feedback transcript。
|
||||||
|
|
||||||
|
因此用户能看到程序文本和当前行,但看不到完整“一行一行执行过程”的历史记录。
|
||||||
|
|
||||||
|
## 9. 正确的 RUN 执行模型建议
|
||||||
|
|
||||||
|
按你提出的标准,正确模型不应靠前端定时器自造轨迹,而应由 task/HAL/motion runtime 产出状态。
|
||||||
|
|
||||||
|
建议目标模型:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. RUN 只发送 LinuxCNC task command:
|
||||||
|
EMC_TASK_SET_STATE ON
|
||||||
|
EMC_TASK_SET_MODE AUTO
|
||||||
|
EMC_TASK_PLAN_RUN
|
||||||
|
|
||||||
|
2. task/HAL runtime 在程序执行期间持续 run task/motion/servo cycles。
|
||||||
|
|
||||||
|
3. 每个 cycle 或每批 cycle 产生 LinuxCNC-owned feedback:
|
||||||
|
activeLine
|
||||||
|
statement/source line
|
||||||
|
axisPose
|
||||||
|
jointPose
|
||||||
|
currentVelocity
|
||||||
|
requestedVelocity
|
||||||
|
distanceToGo / DTG
|
||||||
|
queueDepth
|
||||||
|
interpState
|
||||||
|
cycle / taskCycle / servoCycle
|
||||||
|
switchkinsType / RTCP state
|
||||||
|
|
||||||
|
4. worker 将 feedback 推送给 main thread,或 main thread 通过明确的 runtime pump 读取。
|
||||||
|
|
||||||
|
5. store 只消费 feedback,不自行按 motionIndex/sampleIndex 推进真实运行态。
|
||||||
|
|
||||||
|
6. UI 渲染:
|
||||||
|
- G-code 当前行高亮来自 feedback.line;
|
||||||
|
- DRO 轴值来自 feedback.axisPose 或 kinematics frame;
|
||||||
|
- 速度来自 feedback.currentVelocity;
|
||||||
|
- 程序时间来自 feedback.timeSeconds;
|
||||||
|
- 执行历史记录追加每次 feedback。
|
||||||
|
```
|
||||||
|
|
||||||
|
注意:这里的“pump”可以在 worker 内部完成,也可以由 main thread 调用 runtime pump,但语义必须是 LinuxCNC task/HAL/motion cycles 产生反馈,而不是 UI 自己按 wall clock 或固定 index 插值。
|
||||||
|
|
||||||
|
## 10. 后续测试建议
|
||||||
|
|
||||||
|
建议在 `working_run` 后续补一个严格测试脚本,断言以下行为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 加载一个短 G-code:
|
||||||
|
G90 G21
|
||||||
|
G0 X0 Y0 Z5
|
||||||
|
G1 X10 F600
|
||||||
|
G1 Y10 F300
|
||||||
|
M30
|
||||||
|
|
||||||
|
2. RUN 后采集至少 5 个 feedback tick。
|
||||||
|
|
||||||
|
3. 断言每个 tick 的 sourceMode 都是 linuxcnc-task-motion-hal-wasm。
|
||||||
|
|
||||||
|
4. 断言 activeLine 按 feedback 推进,而不是固定跳到 line 5 后停止。
|
||||||
|
|
||||||
|
5. 断言 servoCycle/taskCycle 单调递增。
|
||||||
|
|
||||||
|
6. 断言 axisPose 与当前 G-code motion 目标/插补位置一致。
|
||||||
|
|
||||||
|
7. 断言 currentVelocity 与当前运动段 F 值/override/task status 对齐。
|
||||||
|
|
||||||
|
8. 断言 UI `.gcode-row.active`、DRO、runtime feedback 文本和 store state 使用同一个 feedback snapshot。
|
||||||
|
|
||||||
|
9. 断言执行历史中能看到逐条 line/tick 记录。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. 本轮验证命令
|
||||||
|
|
||||||
|
已运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm --prefix web-rtcp-5axis-sim-plan/app run build
|
||||||
|
```
|
||||||
|
|
||||||
|
结果:
|
||||||
|
|
||||||
|
```text
|
||||||
|
gmoccapy_static_build=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
浏览器采集脚本在用户中断前已产生完整快照 JSON,未作为正式 smoke 通过声明;本报告只把它作为辅助观察。
|
||||||
|
|
||||||
|
## 12. 总结
|
||||||
|
|
||||||
|
当前 `web-rtcp-5axis-sim-plan` 的 G-code 程序加载、文本显示、interpreter canonical motion、task/HAL 命令发送和 status 映射链路已经存在。
|
||||||
|
|
||||||
|
但当前 `RUN` 的核心行为仍是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
发送命令 -> runCycles 固定 5 个 task cycles -> readStatus 一次 -> 更新 UI 一次
|
||||||
|
```
|
||||||
|
|
||||||
|
它没有形成“G-code 执行期间 task/HAL feedback 持续推送/持续消费”的运行态闭环。因此,按实际时间和进给速度连续执行、真实轴值连续更新、逐行执行过程可见,这三项目前都不能判定为满足。
|
||||||
@@ -0,0 +1,565 @@
|
|||||||
|
# 02 RUN 前置条件、详细测试与编写步骤
|
||||||
|
|
||||||
|
生成时间:2026-06-22
|
||||||
|
|
||||||
|
## 1. 核心原则
|
||||||
|
|
||||||
|
`RUN` 不是孤立按钮逻辑。对五轴机床,必须先确定 LinuxCNC 机床上下文,再执行程序。
|
||||||
|
|
||||||
|
正确顺序是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
明确 INI 文件
|
||||||
|
-> 确定机床类型、坐标轴、joint、HAL、remap、tool-table
|
||||||
|
-> 确定五轴运动学算法和 switchkins 映射
|
||||||
|
-> 验证 kinematics WASM 和 task/HAL runtime 已处在同一机床上下文
|
||||||
|
-> 加载并打开 G-code
|
||||||
|
-> RUN 发送 LinuxCNC task command
|
||||||
|
-> 持续 runCycles/readStatus 或 worker push feedback
|
||||||
|
-> UI 只消费 task/HAL/motion feedback
|
||||||
|
```
|
||||||
|
|
||||||
|
不能先写一个通用 `RUN`,再猜测当前机床是什么。否则 `activeLine`、DRO、RTCP、A/B/C 旋转轴、TCP 补偿和 switchkins 状态都可能来自不同来源。
|
||||||
|
|
||||||
|
## 2. 必须先确认的机床上下文
|
||||||
|
|
||||||
|
当前项目内可作为 `RUN` 上下文的五轴 profile 只有两个:
|
||||||
|
|
||||||
|
| profile | LinuxCNC INI | 机床类型 | 坐标 | kinematics module |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `xyzac-trt` | `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini` | table rotary tilting | `XYZAC` | `xyzac-trt` |
|
||||||
|
| `xyzbc-trt` | `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini` | table rotary tilting | `XYZBC` | `xyzbc-trt` |
|
||||||
|
|
||||||
|
源码落点:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/profiles/index.js
|
||||||
|
app/src/profiles/xyzac-trt.js
|
||||||
|
app/src/profiles/xyzbc-trt.js
|
||||||
|
app/src/runtime/linuxcnc-ini-runtime.js
|
||||||
|
app/src/runtime/linuxcnc-kinematics-runtime.js
|
||||||
|
wasm-port/runtime/sdk/src/linuxcnc-kinematics.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.1 `xyzac-trt`
|
||||||
|
|
||||||
|
必须确认:
|
||||||
|
|
||||||
|
```text
|
||||||
|
profile id: xyzac-trt
|
||||||
|
INI: configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini
|
||||||
|
[EMC] MACHINE: sim-xyzac-trt-kins (switchkins)
|
||||||
|
[TRAJ] COORDINATES: XYZAC
|
||||||
|
[KINS] KINEMATICS: xyzac-trt-kins sparm=identityfirst
|
||||||
|
WASM module id: xyzac-trt
|
||||||
|
WASM file: linuxcnc_xyzac_trt_kinematics.wasm
|
||||||
|
LinuxCNC source: src/emc/kinematics/xyzac-trt-kins.c
|
||||||
|
shared source: src/emc/kinematics/trtfuncs.c
|
||||||
|
switchkins source: src/emc/kinematics/switchkins.c
|
||||||
|
```
|
||||||
|
|
||||||
|
switchkins 映射:
|
||||||
|
|
||||||
|
```text
|
||||||
|
M429 -> switchkins type 0 -> identity
|
||||||
|
M428 -> switchkins type 1 -> tcp-xyzac
|
||||||
|
M430 -> switchkins type 2 -> userk
|
||||||
|
HAL link: motion.analog-out-03 => motion.switchkins-type
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 `xyzbc-trt`
|
||||||
|
|
||||||
|
必须确认:
|
||||||
|
|
||||||
|
```text
|
||||||
|
profile id: xyzbc-trt
|
||||||
|
INI: configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini
|
||||||
|
[EMC] MACHINE: sim-xyzbc-trt-kins (switchkins)
|
||||||
|
[TRAJ] COORDINATES: XYZBC
|
||||||
|
[KINS] KINEMATICS: xyzbc-trt-kins sparm=identityfirst
|
||||||
|
WASM module id: xyzbc-trt
|
||||||
|
WASM file: linuxcnc_xyzbc_trt_kinematics.wasm
|
||||||
|
LinuxCNC source: src/emc/kinematics/xyzbc-trt-kins.c
|
||||||
|
shared source: src/emc/kinematics/trtfuncs.c
|
||||||
|
switchkins source: src/emc/kinematics/switchkins.c
|
||||||
|
```
|
||||||
|
|
||||||
|
switchkins 映射:
|
||||||
|
|
||||||
|
```text
|
||||||
|
M429 -> switchkins type 0 -> identity
|
||||||
|
M428 -> switchkins type 1 -> tcp-xyzbc
|
||||||
|
M430 -> switchkins type 2 -> userk
|
||||||
|
HAL link: motion.analog-out-03 => motion.switchkins-type
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. RUN 前置状态检查
|
||||||
|
|
||||||
|
`RUN` action 进入真正执行前,应要求这些状态全部明确:
|
||||||
|
|
||||||
|
```text
|
||||||
|
state.profile.id 是 xyzac-trt 或 xyzbc-trt
|
||||||
|
state.profile.iniPath 非空且已加载
|
||||||
|
state.iniConfigReadiness.loaded === true
|
||||||
|
state.iniConfigReadiness.ready === true
|
||||||
|
state.iniConfigReadiness.coordinates 与 profile.traj.coordinates 一致
|
||||||
|
state.profile.kinematicsModuleId 与 INI [KINS] KINEMATICS 推导结果一致
|
||||||
|
state.kinematicsRuntimeReadiness.loaded === true
|
||||||
|
state.kinematicsRuntimeReadiness.moduleId === state.profile.kinematicsModuleId
|
||||||
|
state.rtcpFrame.sourceMode === source-derived-kinematics-wasm
|
||||||
|
state.machineFileStaging.status === staged
|
||||||
|
state.taskHalRuntimeReadiness.taskRuntimeReady === true
|
||||||
|
state.taskHalRuntimeReadiness.motionRuntimeReady === true
|
||||||
|
state.taskHalRuntimeReadiness.halRuntimeReady === true
|
||||||
|
state.taskHalSession.programPath 指向当前选中的 G-code
|
||||||
|
```
|
||||||
|
|
||||||
|
如果任一项不满足,`RUN` 应返回明确 operatorMessage,不应进入 fixture line playback 后伪装成真实运行。
|
||||||
|
|
||||||
|
建议阻断消息:
|
||||||
|
|
||||||
|
```text
|
||||||
|
run blocked: LinuxCNC INI not loaded
|
||||||
|
run blocked: machine profile and INI coordinates mismatch
|
||||||
|
run blocked: LinuxCNC kinematics runtime not ready
|
||||||
|
run blocked: task/HAL runtime not ready
|
||||||
|
run blocked: no machine-file G-code opened for task/HAL session
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 详细测试计划
|
||||||
|
|
||||||
|
### 4.1 INI/profile 解析测试
|
||||||
|
|
||||||
|
目标:证明 `RUN` 使用的不是手写机床参数,而是明确 LinuxCNC INI。
|
||||||
|
|
||||||
|
测试文件建议:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tests/node/verify_run_preconditions.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
断言:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 读取 xyzac-trt.ini,parseLinuxCncIni() 返回:
|
||||||
|
profileId=xyzac-trt
|
||||||
|
machineName=sim-xyzac-trt-kins (switchkins)
|
||||||
|
traj.coordinates=XYZAC
|
||||||
|
kinematics.name=xyzac-trt-kins
|
||||||
|
kinematicsModuleId=xyzac-trt
|
||||||
|
validation.ready=true
|
||||||
|
|
||||||
|
2. 读取 xyzbc-trt.ini,parseLinuxCncIni() 返回:
|
||||||
|
profileId=xyzbc-trt
|
||||||
|
machineName=sim-xyzbc-trt-kins (switchkins)
|
||||||
|
traj.coordinates=XYZBC
|
||||||
|
kinematics.name=xyzbc-trt-kins
|
||||||
|
kinematicsModuleId=xyzbc-trt
|
||||||
|
validation.ready=true
|
||||||
|
|
||||||
|
3. applyIniConfigToProfile() 后:
|
||||||
|
profile.traj.coordinates 与 INI 一致
|
||||||
|
profile.kinematicsModuleId 与 INI 推导结果一致
|
||||||
|
axisLimits 和 jointConfig 来自 INI
|
||||||
|
```
|
||||||
|
|
||||||
|
命令:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
通过标准:
|
||||||
|
|
||||||
|
```text
|
||||||
|
run_preconditions_ini_profile_smoke=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 运动学模块确认测试
|
||||||
|
|
||||||
|
目标:证明当前机床的五轴运动学算法已经按 INI/profile 加载。
|
||||||
|
|
||||||
|
断言:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" }) 成功。
|
||||||
|
2. readiness.moduleId === "xyzac-trt"。
|
||||||
|
3. wasmFile === "linuxcnc_xyzac_trt_kinematics.wasm"。
|
||||||
|
4. frameForJoints([10,20,30,25,40]) 返回 moduleId=xyzac-trt。
|
||||||
|
5. switchKinematics(1) 后 switchkinsType=1,frame source 仍是 linuxcnc_kinematics_wasm_c_abi。
|
||||||
|
|
||||||
|
6. 对 xyzbc-trt 重复同样测试。
|
||||||
|
```
|
||||||
|
|
||||||
|
已有相关测试:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_kinematics_runtime.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
新增 RUN 前置测试应把这个结果纳入 `RUN` gate,而不是只作为独立 smoke。
|
||||||
|
|
||||||
|
### 4.3 machine-file staging 测试
|
||||||
|
|
||||||
|
目标:证明 `RUN` 打开的程序、INI、remap、tool table 是同一个 LinuxCNC 机床目录上下文。
|
||||||
|
|
||||||
|
断言:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. stageProfileMachineFiles(profile) 成功。
|
||||||
|
2. save.files 中包含:
|
||||||
|
- 当前 profile 的 INI
|
||||||
|
- 当前 profile 的 tool table
|
||||||
|
- remap_subs/428remap.ngc
|
||||||
|
- remap_subs/429remap.ngc
|
||||||
|
- remap_subs/430remap.ngc
|
||||||
|
- 当前 profile 的 demo G-code
|
||||||
|
3. buildTaskHalSessionFromMachineFiles() 生成:
|
||||||
|
profileId 与 state.profile.id 一致
|
||||||
|
iniPath 与 profile.iniPath 一致
|
||||||
|
programPath 指向当前 selectedGcodeSourceRel
|
||||||
|
```
|
||||||
|
|
||||||
|
已有相关测试:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node web-rtcp-5axis-sim-plan/tests/node/verify_machine_file_staging.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.4 RUN 连续 feedback 测试
|
||||||
|
|
||||||
|
目标:证明点击 `RUN` 后,不是只推进一次 `taskCycles=5`,而是持续从 task/HAL/motion 获取反馈。
|
||||||
|
|
||||||
|
测试程序:
|
||||||
|
|
||||||
|
```ngc
|
||||||
|
G90 G21
|
||||||
|
M429
|
||||||
|
G0 X0 Y0 Z5 A0 C0
|
||||||
|
M428
|
||||||
|
G1 X10 Y0 Z5 A10 C20 F600
|
||||||
|
G1 X10 Y10 Z0 A20 C30 F300
|
||||||
|
M429
|
||||||
|
G1 X0 Y0 Z5 A0 C0 F600
|
||||||
|
M30
|
||||||
|
```
|
||||||
|
|
||||||
|
对 `xyzbc-trt` 时把 A 轴替换为 B 轴:
|
||||||
|
|
||||||
|
```ngc
|
||||||
|
G90 G21
|
||||||
|
M429
|
||||||
|
G0 X0 Y0 Z5 B0 C0
|
||||||
|
M428
|
||||||
|
G1 X10 Y0 Z5 B10 C20 F600
|
||||||
|
G1 X10 Y10 Z0 B20 C30 F300
|
||||||
|
M429
|
||||||
|
G1 X0 Y0 Z5 B0 C0 F600
|
||||||
|
M30
|
||||||
|
```
|
||||||
|
|
||||||
|
采集至少 8 个 feedback tick,断言:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 每个 tick 的 sourceMode 都是 linuxcnc-task-motion-hal-wasm。
|
||||||
|
2. taskCycle 或 servoCycle 单调递增。
|
||||||
|
3. activeLine 随 feedback 推进,不停留在 RUN 后第一帧。
|
||||||
|
4. axisPose 来自同一 tick 的 task/HAL status.ui.axisPose。
|
||||||
|
5. currentVelocityMmPerMin 来自同一 tick 的 motion currentVel。
|
||||||
|
6. M428 后 kinsType 切到 tcp-xyzac 或 tcp-xyzbc。
|
||||||
|
7. M429 后 kinsType 回到 identity。
|
||||||
|
8. UI 当前高亮行、DRO、runtime feedback 文本读取的是同一个 feedback snapshot。
|
||||||
|
9. 程序完成后 runState 变为 complete 或 idle,不继续显示 running。
|
||||||
|
```
|
||||||
|
|
||||||
|
失败判定:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RUN 后 servoCycle 只增加一次,然后 0.5s/2s/5s 不变。
|
||||||
|
activeLine 固定在某一行不再推进。
|
||||||
|
axisPose 从 HOME 位姿被 task-local 0 值覆盖。
|
||||||
|
programRuntimeFeedback.sourceMode 不是 linuxcnc-task-motion-hal-wasm。
|
||||||
|
RTCP 状态与当前 G-code 的 M428/M429 不一致。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.5 浏览器端测试
|
||||||
|
|
||||||
|
建议扩展:
|
||||||
|
|
||||||
|
```text
|
||||||
|
qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
新增 case:
|
||||||
|
|
||||||
|
```text
|
||||||
|
07-run-preconditions-and-feedback
|
||||||
|
```
|
||||||
|
|
||||||
|
浏览器断言:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 首屏等待 state.iniConfigReadiness.loaded === true。
|
||||||
|
2. 选择 xyzac-trt,确认 titlebar/profile/INI/kinematics module 都是 xyzac-trt。
|
||||||
|
3. 点击 POWER、HOME、AUTO。
|
||||||
|
4. 加载上述短 G-code。
|
||||||
|
5. 点击 RUN。
|
||||||
|
6. 采集 t=0.2s、0.5s、1.0s、2.0s、5.0s 状态。
|
||||||
|
7. 断言 task/servo cycle 单调递增。
|
||||||
|
8. 断言 UI `.gcode-row.active` 的 data-program-line 等于 state.activeLine。
|
||||||
|
9. 断言 DRO X/Y/Z/A/C 等于 state.programRuntimeFeedback.axisPose。
|
||||||
|
10. 断言 canvas dataset 的 RTCP 状态与 state.rtcpState 一致。
|
||||||
|
```
|
||||||
|
|
||||||
|
输出证据:
|
||||||
|
|
||||||
|
```text
|
||||||
|
qa/web-rtcp-5axis-site-test/output/run-preconditions-feedback.json
|
||||||
|
qa/web-rtcp-5axis-site-test/screenshots/run-preconditions-feedback/*.png
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 编写 RUN 程序的实现步骤
|
||||||
|
|
||||||
|
### Step 1: 增加 `validateRunPreconditions(state)`
|
||||||
|
|
||||||
|
位置建议:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js
|
||||||
|
```
|
||||||
|
|
||||||
|
职责:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 检查 INI 已加载。
|
||||||
|
2. 检查 profile 与 INI 坐标一致。
|
||||||
|
3. 检查 profile.kinematicsModuleId 与 kinematics runtime moduleId 一致。
|
||||||
|
4. 检查 kinematics runtime ready。
|
||||||
|
5. 检查 task/HAL runtime ready。
|
||||||
|
6. 检查 machine file staging ready。
|
||||||
|
7. 检查 taskHalSession.programPath 指向当前 G-code。
|
||||||
|
```
|
||||||
|
|
||||||
|
返回结构:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
ok: true,
|
||||||
|
profileId: "xyzac-trt",
|
||||||
|
iniPath: ".../xyzac-trt.ini",
|
||||||
|
coordinates: "XYZAC",
|
||||||
|
kinematicsModuleId: "xyzac-trt"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
失败时:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
ok: false,
|
||||||
|
operatorMessage: "run blocked: LinuxCNC INI not loaded"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: `RUN` action 先调用前置检查
|
||||||
|
|
||||||
|
当前 `RUN` 不能直接进入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
runTaskHalCommandSequence([...], { taskCycles: 5 })
|
||||||
|
```
|
||||||
|
|
||||||
|
应先:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. gateLinuxCncTaskAction(state, action)
|
||||||
|
2. validateRunPreconditions(state)
|
||||||
|
3. initializeTaskHalSession({ openProgram: true }) 如需要
|
||||||
|
4. 确认 readStatus() 的 openProgram/programPath 与 state.activeProgram 对齐
|
||||||
|
```
|
||||||
|
|
||||||
|
未满足前置条件时,只设置 `operatorMessage`,不推进任何 line/sample。
|
||||||
|
|
||||||
|
### Step 3: 拆分 task command 与执行 pump
|
||||||
|
|
||||||
|
`RUN` 应只发送 LinuxCNC task 命令:
|
||||||
|
|
||||||
|
```text
|
||||||
|
EMC_TASK_SET_STATE ON
|
||||||
|
EMC_TASK_SET_MODE AUTO
|
||||||
|
EMC_TASK_PLAN_RUN line = activeLine - programStartLine
|
||||||
|
```
|
||||||
|
|
||||||
|
然后启动 feedback pump:
|
||||||
|
|
||||||
|
```text
|
||||||
|
while running:
|
||||||
|
taskHalRuntime.runCycles({ taskCycles: batchSize })
|
||||||
|
status = taskHalRuntime.readStatus()
|
||||||
|
dispatch TASK_HAL_STATUS_APPLIED(status)
|
||||||
|
append feedback history
|
||||||
|
if complete/paused/aborted/stopped:
|
||||||
|
stop pump
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
```text
|
||||||
|
这里的 pump 可以在 store 主线程调度,也可以放到 worker 内部 postMessage 推送。
|
||||||
|
关键不是 timer 本身,而是每个 tick 都必须由 task/HAL/motion runtime 的 runCycles/readStatus 产生反馈。
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: 增加 RUN pump 状态
|
||||||
|
|
||||||
|
建议 state 增加:
|
||||||
|
|
||||||
|
```js
|
||||||
|
taskHalRunPump: {
|
||||||
|
active: false,
|
||||||
|
sequence: 0,
|
||||||
|
profileId: null,
|
||||||
|
iniPath: null,
|
||||||
|
kinematicsModuleId: null,
|
||||||
|
tickCount: 0,
|
||||||
|
lastStatusAt: null,
|
||||||
|
lastError: null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
同时增加:
|
||||||
|
|
||||||
|
```js
|
||||||
|
programRuntimeFeedbackHistory: []
|
||||||
|
```
|
||||||
|
|
||||||
|
历史最多保留 100 到 500 条,避免浏览器长程序内存无限增长。
|
||||||
|
|
||||||
|
### Step 5: `TASK_HAL_STATUS_APPLIED` 只消费 status
|
||||||
|
|
||||||
|
`applyTaskHalStatusPatch()` 应继续负责:
|
||||||
|
|
||||||
|
```text
|
||||||
|
activeLine
|
||||||
|
axisPose
|
||||||
|
kinsType
|
||||||
|
rtcpState
|
||||||
|
runState
|
||||||
|
feed.currentVelocity
|
||||||
|
programRuntimeFeedback
|
||||||
|
```
|
||||||
|
|
||||||
|
但需要补充:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. programRuntimeFeedbackHistory append 当前 feedback。
|
||||||
|
2. 记录 feedback.profileId、iniPath、kinematicsModuleId。
|
||||||
|
3. 对 ui.axisPoseFrame 做严格处理,避免 task-local 0 值覆盖 HOME/work pose。
|
||||||
|
4. 运行态 axisPose、velocity、DTG、activeLine 必须来自同一个 status snapshot。
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: STOP/ABORT/PAUSE/RESUME 控制 pump
|
||||||
|
|
||||||
|
行为要求:
|
||||||
|
|
||||||
|
```text
|
||||||
|
STOP/ABORT:
|
||||||
|
send EMC_TASK_ABORT
|
||||||
|
stop pump
|
||||||
|
read final status
|
||||||
|
|
||||||
|
PAUSE:
|
||||||
|
send EMC_TASK_PLAN_PAUSE
|
||||||
|
pump 可停止或降频读取 paused status
|
||||||
|
runState=paused
|
||||||
|
|
||||||
|
RESUME:
|
||||||
|
send EMC_TASK_PLAN_RESUME
|
||||||
|
restart pump
|
||||||
|
|
||||||
|
STEP:
|
||||||
|
不走 fixture sample playback
|
||||||
|
发送/推进一个明确的 task cycle batch
|
||||||
|
readStatus 一次
|
||||||
|
runState=stepping 或 paused
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 7: UI 显示执行历史
|
||||||
|
|
||||||
|
在 `app/src/ui/gmoccapy-shell.js` 的 info/sidebar 区域增加:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Last feedback:
|
||||||
|
sourceMode / sample / line / taskCycle / servoCycle / velocity / kinsType
|
||||||
|
|
||||||
|
Run history:
|
||||||
|
最近 10 条 line/tick
|
||||||
|
```
|
||||||
|
|
||||||
|
UI 只显示 `programRuntimeFeedbackHistory`,不自行推导行号或坐标。
|
||||||
|
|
||||||
|
### Step 8: 移除真实 RUN 的 fixture fallback 混淆
|
||||||
|
|
||||||
|
保留 fallback 可以用于开发,但必须明确标记:
|
||||||
|
|
||||||
|
```text
|
||||||
|
programExecutionSourceMode=fixture-line-playback
|
||||||
|
operatorMessage=LinuxCNC task/HAL unavailable; fixture playback only
|
||||||
|
fullLinuxCncProgramExecutionReady=false
|
||||||
|
```
|
||||||
|
|
||||||
|
在真实 `taskHalRuntime.loaded === true` 且前置条件失败时,不应退回 fixture playback。
|
||||||
|
|
||||||
|
## 6. 开发验收命令
|
||||||
|
|
||||||
|
基础检查:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
|
||||||
|
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_kinematics_runtime.mjs
|
||||||
|
node web-rtcp-5axis-sim-plan/tests/node/verify_machine_file_staging.mjs
|
||||||
|
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_task_hal_runtime.mjs
|
||||||
|
npm --prefix web-rtcp-5axis-sim-plan/app run build
|
||||||
|
```
|
||||||
|
|
||||||
|
完整 node smoke:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
|
||||||
|
```
|
||||||
|
|
||||||
|
浏览器 smoke:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_shell_browser.sh
|
||||||
|
bash web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
QA 证据采集:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. 完成标准
|
||||||
|
|
||||||
|
`RUN` 可以判定为完善,必须同时满足:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 每次 RUN 都能追溯到明确 INI。
|
||||||
|
2. 每次 RUN 都能说明机床类型和坐标轴。
|
||||||
|
3. 每次 RUN 都能说明使用哪个 LinuxCNC 五轴 kinematics module。
|
||||||
|
4. M428/M429/M430 的 switchkins 状态来自 LinuxCNC remap/HAL/task feedback 链。
|
||||||
|
5. activeLine、DRO、速度、DTG、程序时间来自同一个 task/HAL/motion feedback snapshot。
|
||||||
|
6. RUN 后 taskCycle/servoCycle 随时间持续推进,直到 paused/stopped/complete。
|
||||||
|
7. UI 高亮行和执行历史能复现逐行执行过程。
|
||||||
|
8. fallback playback 不能冒充 LinuxCNC task/HAL runtime。
|
||||||
|
```
|
||||||
|
|
||||||
|
未满足以上条件时,状态应继续标记为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
fullLinuxCncProgramExecutionReady=false
|
||||||
|
hardwareDrive=false
|
||||||
|
hostRealtimeKernel=false
|
||||||
|
externalUserMProcessReady=false
|
||||||
|
```
|
||||||
@@ -0,0 +1,311 @@
|
|||||||
|
# 03 RUN 第一步完成记录与下一步建议
|
||||||
|
|
||||||
|
生成时间:2026-06-22
|
||||||
|
|
||||||
|
## 1. 本轮完成范围
|
||||||
|
|
||||||
|
本轮只完成 `02-run-preconditions-test-and-implementation-steps.md` 中的第一步和第二步:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Step 1: 增加 validateRunPreconditions(state)
|
||||||
|
Step 2: RUN action 先调用前置检查
|
||||||
|
```
|
||||||
|
|
||||||
|
本轮没有实现连续 feedback pump。也没有把 `RUN` 宣称为完整实时运行模型。
|
||||||
|
|
||||||
|
## 2. 已完成改动
|
||||||
|
|
||||||
|
### 2.1 RUN 前置条件 gate
|
||||||
|
|
||||||
|
新增源码:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/state/store.js
|
||||||
|
export function validateRunPreconditions(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
校验内容:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. profile 必须是 xyzac-trt 或 xyzbc-trt。
|
||||||
|
2. LinuxCNC INI 必须已加载并 validation.ready=true。
|
||||||
|
3. profile.iniPath 必须与已加载 INI path 一致。
|
||||||
|
4. profile TRAJ coordinates 必须与 INI coordinates 一致。
|
||||||
|
5. profile.kinematicsModuleId 必须与 INI [KINS] 推导结果一致。
|
||||||
|
6. LinuxCNC kinematics runtime 必须 loaded。
|
||||||
|
7. kinematics runtime moduleId 必须匹配当前 profile。
|
||||||
|
8. RTCP frame 必须来自 source-derived-kinematics-wasm。
|
||||||
|
9. machine files 必须 staged。
|
||||||
|
10. 必须已选择 LinuxCNC source-directory G-code。
|
||||||
|
11. task/HAL runtime 必须 ready。
|
||||||
|
12. requireTaskHalSession=true 时,taskHalSession.programPath 必须匹配当前 selected G-code。
|
||||||
|
```
|
||||||
|
|
||||||
|
关键结果:
|
||||||
|
|
||||||
|
```text
|
||||||
|
当 taskHalRuntime 已加载时,RUN 不再直接发送 EMC_TASK_PLAN_RUN。
|
||||||
|
RUN 会先校验 INI/profile/kinematics/machine-file/task-HAL 上下文。
|
||||||
|
前置条件不满足时,只给 operatorMessage,不进入 fixture playback。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 RUN 使用真实 machine-file session
|
||||||
|
|
||||||
|
新增内部流程:
|
||||||
|
|
||||||
|
```text
|
||||||
|
runValidatedTaskHalProgramRun()
|
||||||
|
```
|
||||||
|
|
||||||
|
执行顺序:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. validateRunPreconditions(..., requireTaskHalSession=false)
|
||||||
|
2. 如 taskHalSession 缺失或 programPath 不匹配,initializeTaskHalSession({ openProgram: true })
|
||||||
|
3. validateRunPreconditions(..., requireTaskHalSession=true)
|
||||||
|
4. 发送:
|
||||||
|
EMC_TASK_SET_STATE ON
|
||||||
|
EMC_TASK_SET_MODE AUTO
|
||||||
|
EMC_TASK_PLAN_RUN line = activeLine - programStartLine
|
||||||
|
5. runTaskHalCommandSequence(..., allowFixtureSession=false)
|
||||||
|
```
|
||||||
|
|
||||||
|
这一步解决的是“RUN 之前先确定明确 INI、机床类型和五轴运动学算法”,不是连续执行问题。
|
||||||
|
|
||||||
|
### 2.3 新增测试
|
||||||
|
|
||||||
|
新增:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tests/node/verify_run_preconditions.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
覆盖:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. xyzac-trt.ini 解析:
|
||||||
|
MACHINE=sim-xyzac-trt-kins (switchkins)
|
||||||
|
COORDINATES=XYZAC
|
||||||
|
KINEMATICS=xyzac-trt-kins
|
||||||
|
kinematicsModuleId=xyzac-trt
|
||||||
|
|
||||||
|
2. xyzbc-trt.ini 解析:
|
||||||
|
MACHINE=sim-xyzbc-trt-kins (switchkins)
|
||||||
|
COORDINATES=XYZBC
|
||||||
|
KINEMATICS=xyzbc-trt-kins
|
||||||
|
kinematicsModuleId=xyzbc-trt
|
||||||
|
|
||||||
|
3. applyIniConfigToProfile() 后 profile 与 INI 保持一致。
|
||||||
|
4. createLinuxCncKinematicsRuntime() 可按 moduleId 加载对应 WASM。
|
||||||
|
5. stageProfileMachineFiles() 和 buildTaskHalSessionFromMachineFiles() 能形成同一 machine-file 上下文。
|
||||||
|
6. validateRunPreconditions() 能阻断未加载 INI 的初始状态。
|
||||||
|
7. validateRunPreconditions() 能在 INI/kinematics/staged G-code ready 时通过非 task-HAL session 预检。
|
||||||
|
8. task/HAL runtime 缺失时返回明确阻断消息。
|
||||||
|
```
|
||||||
|
|
||||||
|
`app/package.json` 的 `smoke:node` 已加入:
|
||||||
|
|
||||||
|
```text
|
||||||
|
node ../tests/node/verify_run_preconditions.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 调整 task/HAL smoke
|
||||||
|
|
||||||
|
更新:
|
||||||
|
|
||||||
|
```text
|
||||||
|
tests/node/verify_linuxcnc_task_hal_runtime.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
调整点:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 不再用内联 fixture G-code 直接测试真实 RUN。
|
||||||
|
2. 加载 xyzac-trt INI。
|
||||||
|
3. 加载 xyzac-trt kinematics runtime。
|
||||||
|
4. stage machine files。
|
||||||
|
5. LOAD_LINUXCNC_GCODE_SOURCE 选择:
|
||||||
|
configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/xyzac_switchkins_test_1.ngc
|
||||||
|
6. 等待 taskHalSession.programPath 指向该 G-code。
|
||||||
|
7. 再执行 POWER/HOME/AUTO/RUN。
|
||||||
|
```
|
||||||
|
|
||||||
|
这样 task/HAL RUN smoke 与新的 RUN 前置条件一致。
|
||||||
|
|
||||||
|
## 3. 已运行验证
|
||||||
|
|
||||||
|
通过:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node web-rtcp-5axis-sim-plan/tests/node/verify_run_preconditions.mjs
|
||||||
|
node web-rtcp-5axis-sim-plan/tests/node/verify_linuxcnc_task_hal_runtime.mjs
|
||||||
|
npm --prefix web-rtcp-5axis-sim-plan/app run build
|
||||||
|
```
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
```text
|
||||||
|
run_preconditions_ini_profile_smoke=ok
|
||||||
|
run_preconditions_kinematics_smoke=ok
|
||||||
|
run_preconditions_machine_file_smoke=ok
|
||||||
|
|
||||||
|
linuxcnc_task_hal_runtime_smoke=ok
|
||||||
|
task_hal_machine_file_smoke=ok
|
||||||
|
switchkins_remap_hal_sync_smoke=ok
|
||||||
|
browser_task_hal_worker_smoke=ok
|
||||||
|
|
||||||
|
gmoccapy_static_build=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
部分通过后失败:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm --prefix web-rtcp-5axis-sim-plan/app run smoke:node
|
||||||
|
```
|
||||||
|
|
||||||
|
该命令已通过本轮新增和相关测试:
|
||||||
|
|
||||||
|
```text
|
||||||
|
linuxcnc_kinematics_runtime_smoke=ok
|
||||||
|
linuxcnc_interpreter_runtime_smoke=ok
|
||||||
|
linuxcnc_ini_runtime_smoke=ok
|
||||||
|
run_preconditions_ini_profile_smoke=ok
|
||||||
|
run_preconditions_kinematics_smoke=ok
|
||||||
|
run_preconditions_machine_file_smoke=ok
|
||||||
|
linuxcnc_task_hal_runtime_smoke=ok
|
||||||
|
task_hal_machine_file_smoke=ok
|
||||||
|
switchkins_remap_hal_sync_smoke=ok
|
||||||
|
browser_task_hal_worker_smoke=ok
|
||||||
|
```
|
||||||
|
|
||||||
|
随后在既有 native audit 停止:
|
||||||
|
|
||||||
|
```text
|
||||||
|
wasm-port/tests/native/verify_task_hal_phase0.sh failed with status 1
|
||||||
|
```
|
||||||
|
|
||||||
|
直接运行该脚本确认失败点是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
wasm-port/tools/verify_task_hal_source_manifest.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
日志:
|
||||||
|
|
||||||
|
```text
|
||||||
|
task_hal_source_manifest_ready=0
|
||||||
|
task_hal_reference_source_ready=0
|
||||||
|
task_hal_missing_reference_source_count=20
|
||||||
|
```
|
||||||
|
|
||||||
|
说明:该失败来自外部 reference source `../linuxcnc` 缺失 task/HAL manifest 需要的 20 个文件,不是本轮 RUN 前置条件改动造成。
|
||||||
|
|
||||||
|
## 4. 当前 RUN 状态
|
||||||
|
|
||||||
|
现在 `RUN` 的第一道边界已经收紧:
|
||||||
|
|
||||||
|
```text
|
||||||
|
没有明确 INI -> 不 RUN
|
||||||
|
profile/INI 坐标不一致 -> 不 RUN
|
||||||
|
kinematics module 不一致 -> 不 RUN
|
||||||
|
kinematics frame 未 ready -> 不 RUN
|
||||||
|
machine files 未 staged -> 不 RUN
|
||||||
|
没有 LinuxCNC source G-code -> 不 RUN
|
||||||
|
task/HAL runtime 未 ready -> 不 RUN
|
||||||
|
taskHalSession.programPath 不匹配当前 G-code -> 不 RUN
|
||||||
|
```
|
||||||
|
|
||||||
|
但当前 `RUN` 仍然是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
发送 task command
|
||||||
|
-> runCycles({ taskCycles: 5 })
|
||||||
|
-> readStatus() 一次
|
||||||
|
-> TASK_HAL_STATUS_APPLIED 一次
|
||||||
|
```
|
||||||
|
|
||||||
|
所以 `01-run-gcode-execution-principle-and-test.md` 中指出的连续 feedback 问题仍未解决。
|
||||||
|
|
||||||
|
## 5. 下一步工作建议
|
||||||
|
|
||||||
|
下一步应实现 `02` 文件中的 Step 3 和 Step 4:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Step 3: 拆分 task command 与执行 pump
|
||||||
|
Step 4: 增加 RUN pump 状态
|
||||||
|
```
|
||||||
|
|
||||||
|
建议最小实现范围:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 在 store state 增加 taskHalRunPump:
|
||||||
|
active
|
||||||
|
sequence
|
||||||
|
profileId
|
||||||
|
iniPath
|
||||||
|
kinematicsModuleId
|
||||||
|
tickCount
|
||||||
|
lastStatusAt
|
||||||
|
lastError
|
||||||
|
|
||||||
|
2. 增加 programRuntimeFeedbackHistory,最多保留 100 条。
|
||||||
|
|
||||||
|
3. RUN 发送 EMC_TASK_PLAN_RUN 后,不只 runCycles 5 次。
|
||||||
|
改为启动 pump:
|
||||||
|
runCycles({ taskCycles: batchSize })
|
||||||
|
readStatus()
|
||||||
|
dispatch TASK_HAL_STATUS_APPLIED
|
||||||
|
append feedback history
|
||||||
|
根据 interpState / task.nextProgramLine / openedLineCount / paused / aborted 停止。
|
||||||
|
|
||||||
|
4. STOP/ABORT 必须停止 pump。
|
||||||
|
|
||||||
|
5. PAUSE 停止或降频 pump,并保留 paused status。
|
||||||
|
|
||||||
|
6. RESUME 重新启动 pump。
|
||||||
|
|
||||||
|
7. STEP 在 task/HAL runtime loaded 时不走 fixture sample playback,
|
||||||
|
而是推进一个明确 task cycle batch 并 readStatus 一次。
|
||||||
|
```
|
||||||
|
|
||||||
|
下一步测试建议:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 新增 tests/node/verify_run_feedback_pump.mjs。
|
||||||
|
2. 使用 xyzac_switchkins_test_1.ngc。
|
||||||
|
3. RUN 后采集至少 8 个 feedback tick。
|
||||||
|
4. 断言 taskCycle 或 servoCycle 单调递增。
|
||||||
|
5. 断言 activeLine 不停留在 RUN 后第一帧。
|
||||||
|
6. 断言每个 tick 的 sourceMode 都是 linuxcnc-task-motion-hal-wasm。
|
||||||
|
7. 断言 programRuntimeFeedbackHistory 至少包含 3 条。
|
||||||
|
8. 断言 STOP 后 pump.active=false。
|
||||||
|
```
|
||||||
|
|
||||||
|
浏览器测试建议:
|
||||||
|
|
||||||
|
```text
|
||||||
|
扩展 qa/web-rtcp-5axis-site-test/capture-toolpath-preview-cases.mjs
|
||||||
|
新增 07-run-preconditions-and-feedback case。
|
||||||
|
采集 RUN 后 0.2s / 0.5s / 1.0s / 2.0s 的 state。
|
||||||
|
断言 activeLine、DRO、runtime feedback、canvas RTCP 状态来自同一 feedback snapshot。
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 重要边界
|
||||||
|
|
||||||
|
下一步实现 pump 时仍需保持:
|
||||||
|
|
||||||
|
```text
|
||||||
|
不引入 JS G-code parser。
|
||||||
|
不在 UI 里自增 activeLine。
|
||||||
|
不在 UI 里自造 axisPose。
|
||||||
|
不把 canonical timing estimate 当作真实 task/HAL runtime。
|
||||||
|
不把 fixture playback 标记为 fullLinuxCncProgramExecutionReady。
|
||||||
|
```
|
||||||
|
|
||||||
|
RUN 的真实推进必须来自:
|
||||||
|
|
||||||
|
```text
|
||||||
|
taskHalRuntime.runCycles()
|
||||||
|
taskHalRuntime.readStatus()
|
||||||
|
TASK_HAL_STATUS_APPLIED
|
||||||
|
```
|
||||||
54
web-rtcp-5axis-sim-plan/working_run/README.md
Normal file
54
web-rtcp-5axis-sim-plan/working_run/README.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# working_run 运行功能测试与原理记录
|
||||||
|
|
||||||
|
生成时间:2026-06-22
|
||||||
|
|
||||||
|
本目录记录 `web-rtcp-5axis-sim-plan` 本地项目中 G-code 程序加载、解释、`RUN` 执行链路、task/HAL feedback 消费方式,以及当前测试发现的问题。
|
||||||
|
|
||||||
|
## 文件清单
|
||||||
|
|
||||||
|
| 文件 | 用途 |
|
||||||
|
| --- | --- |
|
||||||
|
| `01-run-gcode-execution-principle-and-test.md` | G-code 具体执行过程、`RUN` 执行原理、源码证据、测试观察和问题结论 |
|
||||||
|
| `02-run-preconditions-test-and-implementation-steps.md` | `RUN` 前置条件、INI/机床/五轴运动学确认流程、详细测试用例和编写 `RUN` 程序步骤 |
|
||||||
|
| `03-run-step1-preconditions-complete-next-steps.md` | 本轮已完成的 `RUN` 前置条件 gate、验证结果和下一步接续建议 |
|
||||||
|
|
||||||
|
## 结论摘要
|
||||||
|
|
||||||
|
`RUN` 不应作为第一步实现。正确顺序必须是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
1. 先确定明确的 LinuxCNC INI 文件。
|
||||||
|
2. 由 INI/profile 确定机床类型、坐标轴、joint 配置、HAL/remap/tool-table 资产。
|
||||||
|
3. 由 INI 的 [KINS] KINEMATICS 确定五轴运动学算法和 switchkins 映射。
|
||||||
|
4. 验证对应 LinuxCNC kinematics WASM 已加载,并能产生 source-derived frame。
|
||||||
|
5. 之后才进入 RUN:打开同一 INI 上下文下的 G-code,发送 task command,持续消费 task/HAL/motion feedback。
|
||||||
|
```
|
||||||
|
|
||||||
|
当前项目明确支持的五轴机床上下文是:
|
||||||
|
|
||||||
|
| profile | INI | 机床类型 | 坐标 | 运动学 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `xyzac-trt` | `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini` | table rotary tilting | `XYZAC` | `xyzac-trt-kins sparm=identityfirst` |
|
||||||
|
| `xyzbc-trt` | `configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini` | table rotary tilting | `XYZBC` | `xyzbc-trt-kins sparm=identityfirst` |
|
||||||
|
|
||||||
|
当前实现已经能显示 G-code 程序文本、当前行、高亮行、DRO、runtime feedback 和 task/HAL 状态。
|
||||||
|
|
||||||
|
但按 `textbak` 接续文件的边界要求,当前 `RUN` 还不是连续的 task/HAL feedback 推送执行模型:
|
||||||
|
|
||||||
|
```text
|
||||||
|
RUN 点击后:
|
||||||
|
1. UI 发送 EMC_TASK_SET_STATE / EMC_TASK_SET_MODE / EMC_TASK_PLAN_RUN;
|
||||||
|
2. store 调用 taskHalRuntime.runCycles({ taskCycles: 5 });
|
||||||
|
3. 随后 readStatus() 一次;
|
||||||
|
4. TASK_HAL_STATUS_APPLIED 将这一次 status 映射到 activeLine / axisPose / velocity / feedback;
|
||||||
|
5. 没有看到 worker 主动持续 postMessage 推送 status;
|
||||||
|
6. 如果没有再次调用 runCycles/readStatus,状态不会继续随真实时间推进。
|
||||||
|
```
|
||||||
|
|
||||||
|
因此,当前问题重点不是“前端是否需要定时器”,而是:
|
||||||
|
|
||||||
|
```text
|
||||||
|
task/HAL runtime 是否能在 G-code 执行期间持续产出状态;
|
||||||
|
UI/store 是否持续消费这些 LinuxCNC-owned 状态;
|
||||||
|
轴值、当前行、速度、DTG、程序时间是否都来自同一条 task/HAL/motion feedback 链。
|
||||||
|
```
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 198 KiB |
Reference in New Issue
Block a user