Add RUN precondition gate for 5-axis simulation
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
"build": "node scripts/build-static.mjs",
|
||||
"dev": "python3 -m http.server 4173",
|
||||
"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": {},
|
||||
"devDependencies": {}
|
||||
|
||||
@@ -1064,11 +1064,12 @@ export function createSimulationStore(seed = {}) {
|
||||
break;
|
||||
}
|
||||
if (state.taskHalRuntime?.loaded) {
|
||||
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) - 1, 0) },
|
||||
], { taskCycles: 5, operatorMessage: "task/HAL program run" }).catch(() => {});
|
||||
const preconditions = validateRunPreconditions(state, { requireTaskHalSession: false });
|
||||
if (!preconditions.ok) {
|
||||
setState({ operatorMessage: preconditions.operatorMessage });
|
||||
break;
|
||||
}
|
||||
runValidatedTaskHalProgramRun().catch(() => {});
|
||||
break;
|
||||
}
|
||||
const playback = nextProgramRuntimeSamplePlayback(state, 5);
|
||||
@@ -1569,12 +1570,42 @@ export function createSimulationStore(seed = {}) {
|
||||
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, {
|
||||
taskCycles = 1,
|
||||
taskPeriodNs = 10000000,
|
||||
servoPeriodNs = 1000000,
|
||||
operatorMessage = "task/HAL command complete",
|
||||
pendingJogCommand = null,
|
||||
allowFixtureSession = true,
|
||||
} = {}) => {
|
||||
if (!state.taskHalRuntime?.loaded) {
|
||||
throw new Error("LinuxCNC task/HAL runtime not attached");
|
||||
@@ -1589,9 +1620,12 @@ export function createSimulationStore(seed = {}) {
|
||||
try {
|
||||
if (!state.taskHalSession && state.machineFileStaging?.save?.files?.length) {
|
||||
await initializeTaskHalSession({ openProgram: true });
|
||||
} else if (!state.taskHalSession && state.programLines?.length) {
|
||||
} else if (allowFixtureSession && !state.taskHalSession && state.programLines?.length) {
|
||||
await initializeFixtureTaskHalSessionForState();
|
||||
}
|
||||
if (!state.taskHalSession) {
|
||||
throw new Error("LinuxCNC task/HAL session not initialized");
|
||||
}
|
||||
for (const command of commands) {
|
||||
await state.taskHalRuntime.sendCommand(command);
|
||||
}
|
||||
@@ -1773,6 +1807,142 @@ function isAsyncKinematicsRuntime(runtime) {
|
||||
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) {
|
||||
const ui = status?.ui || {};
|
||||
const task = status?.task || {};
|
||||
|
||||
Reference in New Issue
Block a user