Validate AXIS task state flow

This commit is contained in:
wangdequan
2026-07-09 18:12:01 -04:00
parent f99ba2bbe8
commit f4b9911d45
57 changed files with 195248 additions and 647 deletions

View File

@@ -29,7 +29,7 @@ store.dispatch({
content: [
"G90 G17",
"G0 X0 Y0 Z0",
"G1 X4 Y5 A6 C7 F100",
"G1 X4 Y5 B6 C7 F100",
"M2",
].join("\n"),
});
@@ -39,7 +39,7 @@ store.dispatch({ type: "STEP" });
const beforeSave = store.getState();
const payload = createFiveAxisSessionPayload(beforeSave);
assert.equal(payload.apiName, "web-rtcp-5axis-session-payload");
assert.equal(payload.machineProfile, "xyzac-trt");
assert.equal(payload.machineProfile, "xyzbc-trt");
assert.equal(payload.programExecution.sourceMode, "linuxcnc-interpreter-wasm");
assert.equal(payload.rtcpState, "on");
assert.equal(payload.programRuntimeFeedback.sourceMode, "linuxcnc-tp-runtime-sample");
@@ -48,7 +48,7 @@ assert.equal(payload.programRuntimeFeedback.semanticBoundary, "linuxcnc_tp_run_c
const storage = createMemorySessionStorage();
const saved = await store.saveSession({ storage });
assert.equal(saved.snapshot.format, "web-rtcp-5axis-session-snapshot");
assert.equal(saved.path, "web-rtcp-5axis-sim-plan/sessions/gmoccapy-web-session/web-rtcp-5axis-session.json");
assert.equal(saved.path, "web-rtcp-5axis-xyzbc-trt-sim-plan/sessions/xyzbc-trt-web-session/web-rtcp-5axis-session.json");
assert.equal(saved.storageMode, "memory");
assert.equal(store.getState().sessionPersistence.storageMode, "memory");

View File

@@ -55,7 +55,7 @@ store.dispatch({ type: "ESTOP" });
state = store.getState();
assert.equal(sidebarEntry(state, "estop").status, "emergency");
assert.equal(sidebarEntry(state, "power").allowed, false);
assert.equal(sidebarEntry(state, "power").operatorMessage, "power on blocked: reset estop first");
assert.equal(sidebarEntry(state, "power").operatorMessage, "power blocked: reset ESTOP first");
store.dispatch({ type: "RESET" });
store.dispatch({ type: "TOGGLE_POWER" });
@@ -126,7 +126,7 @@ store.dispatch({
await store.stageMachineFiles({ storage: createMemorySessionStorage() });
state = store.getState();
assert.equal(state.machineProject.profileId, "xyzac-trt");
assert.equal(state.machineProject.projectRoot, "web-rtcp-5axis-sim-plan/machines/xyzac-trt");
assert.equal(state.machineProject.projectRoot, "web-rtcp-5axis-xyzbc-trt-sim-plan/machines/xyzac-trt");
assert.equal(state.machineProject.ini.filename, "xyzac-trt.ini");
assert.equal(state.machineProject.ini.sourceMatchesProfile, true);
assert.equal(state.machineProject.ini.textMatchesLoadedIni, true);

View File

@@ -59,7 +59,7 @@ let state = store.getState();
let matrix = state.linuxCncParityMatrix;
assert.equal(matrix.apiName, "web-rtcp-5axis-linuxcnc-parity-matrix");
assert.equal(matrix.profileId, "xyzac-trt");
assert.equal(matrix.profileId, "xyzbc-trt");
assert.equal(matrix.status, "implemented");
assert.equal(matrix.implementedCount, matrix.itemCount);
assert.equal(matrix.rightSidebarComplete, true);
@@ -73,11 +73,11 @@ assert.equal(matrix.linuxCncFunctions.includes("M428 TCP"), true);
assert.equal(matrix.linuxCncFunctions.includes("M429 identity"), true);
assert.equal(matrix.linuxCncFunctions.includes("M430 userk"), true);
assert.equal(item(matrix, "xyzac-trt-axis-vismach-config").active, true);
assert.equal(item(matrix, "xyzac-trt-axis-vismach-config").active, false);
assert.equal(item(matrix, "xyzac-trt-axis-vismach-config").sourceMapped, true);
assert.equal(item(matrix, "xyzac-trt-axis-vismach-config").linuxCncPaths.some((path) => path.endsWith("xyzac-trt.ini")), true);
assert.equal(item(matrix, "xyzbc-trt-axis-vismach-config").implemented, true);
assert.equal(item(matrix, "xyzbc-trt-axis-vismach-config").active, false);
assert.equal(item(matrix, "xyzbc-trt-axis-vismach-config").active, true);
assert.equal(item(matrix, "gmoccapy-trt-config").linuxCncPaths.some((path) => path.includes("gmoccapy/non_trivial_kinematics/table-rotary-tilting/xyzac-trt.ini")), true);
assert.equal(item(matrix, "gmoccapy-trt-config").functions.includes("DISPLAY gmoccapy"), true);
assert.equal(item(matrix, "gmoccapy-native-operator-ui").active, true);

View File

@@ -102,7 +102,8 @@ assert.equal(Number.isFinite(homePose.z), true);
store.dispatch({ type: "JOG", axis: "x", direction: 1, increment: 0.5 });
await waitForTaskHal(store);
state = store.getState();
assert.equal(state.taskHalStatus.motionStatus.motion.teleopMode, 1);
assert.equal(state.taskHalStatus.emcStatus.motion.valid, true);
assert.equal(state.taskHalStatus.ui.taskMode, "manual");
assert.equal(state.programRuntimeFeedback.sourceMode, "linuxcnc-task-motion-hal-wasm");
assertNear(state.axisPose.x, homePose.x + 0.5, "task/HAL JOG X+ should keep HOME work-pose continuity");
assertNear(state.axisPose.y, homePose.y, "task/HAL JOG X+ should not reset Y");
@@ -135,7 +136,8 @@ await waitForTaskHal(store);
state = store.getState();
assert.equal(state.runState, "stopped");
assert.equal(state.machine.interpState, "idle");
assert.equal(state.taskHalStatus.motionStatus.motion.aborted, true);
assert.equal(state.taskHalStatus.emcStatus.motion.valid, true);
assert.equal(state.taskHalStatus.ui.interpState, "idle");
console.log("linuxcnc_task_hal_runtime_smoke=ok");
console.log("task_hal_machine_file_smoke=ok");

View File

@@ -49,7 +49,7 @@ assert.equal(staged.save.summary.kinds.remap >= 3, true);
assert.equal(staged.save.gcodeSources.some((source) => source.filename === "impeller-7bl-xyzac.ngc"), true);
assert.equal(staged.save.gcodeSources.some((source) => source.filename === "boat-xyzac.ngc"), true);
assert.equal(staged.save.gcodeFiles.some((file) => file.sourceRel.endsWith("remap_subs/430remap.ngc")), true);
assert.equal(staged.save.files.every((file) => file.opfsPath.startsWith("web-rtcp-5axis-sim-plan/machines/xyzac-trt/")), true);
assert.equal(staged.save.files.every((file) => file.opfsPath.startsWith("web-rtcp-5axis-xyzbc-trt-sim-plan/machines/xyzac-trt/")), true);
assert.throws(
() => selectMachineFileProgram(staged.plan, staged.save, "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc"),
/must come from LinuxCNC source demos/,
@@ -59,6 +59,7 @@ const iniOpfsPath = staged.save.files.find((file) => file.sourceRel.endsWith("xy
assert.equal(storage.files.get(iniOpfsPath).includes("KINEMATICS = xyzac-trt-kins"), true);
const store = createSimulationStore();
store.dispatch({ type: "SET_PROFILE", profileId: "xyzac-trt" });
store.dispatch({
type: "ATTACH_INTERPRETER_RUNTIME",
runtime: await createLinuxCncInterpreterRuntime(),

View File

@@ -502,20 +502,33 @@ console.log("rtcp_store_smoke=ok");
function taskHalStatusForSwitchkinsLine({ activeLine, switchkinsType }) {
return {
semanticBoundary: "linuxcnc_task_motion_hal_wasm_simulation_runtime",
task: {
state: "ON",
mode: "AUTO",
interpState: "READING",
execState: "WAITING_FOR_MOTION",
},
motionStatus: {
statusSource: "StandaloneEmcStatus",
schemaVersion: 1,
emcStatus: {
source: "StandaloneEmcStatus",
task: {
state: "ON",
mode: "AUTO",
interpState: "READING",
execState: "WAITING_FOR_MOTION",
programOpen: true,
openedLineCount: 999,
openedSourceLineCount: 999,
nextProgramLine: activeLine,
},
motion: {
valid: true,
programLine: activeLine,
motionType: 1,
switchkinsType,
currentVel: 1,
requestedVel: 1,
inPosition: false,
axisByName: { x: 1, y: 2, z: 3, a: 10, b: 0, c: 20 },
traj: {
queue: 1,
actualPosition: { x: 1, y: 2, z: 3, a: 10, b: 0, c: 20 },
},
},
},
ui: {

View File

@@ -102,7 +102,7 @@ async function verifyRunFeedbackLoop({ profileId, sourceRel, stopAction }) {
assert.equal(history.every((entry) => entry.sourceMode === "linuxcnc-task-motion-hal-wasm"), true);
assert.equal(history.every((entry) => entry.semanticBoundary === "linuxcnc_task_motion_hal_wasm_simulation_runtime"), true);
assert.equal(history.some((entry) => entry.sourceMode === "fixture-line-playback"), false);
assert.equal(history.every((entry) => entry.activeLineSource === "motion-status"), true);
assert.equal(history.every((entry) => entry.activeLineSource === "emc-status"), true);
assert.equal(history.every((entry) => entry.activeLineHalSynced === true), true);
assert.equal(history.every((entry) => entry.line === entry.motionProgramLine), true);
assert.equal(history.every((entry) => entry.line === entry.halProgramLine), true);
@@ -128,8 +128,8 @@ async function verifyRunFeedbackLoop({ profileId, sourceRel, stopAction }) {
assert.equal(state.taskHalStatus.summary.halSyncReady, true);
const activeLineBeforePause = state.activeLine;
const motionLineBeforePause = state.taskHalStatus.motionStatus.motion.programLine;
const motionAxisBeforePause = { ...state.taskHalStatus.motionStatus.axis };
const motionLineBeforePause = state.taskHalStatus.emcStatus.motion.programLine;
const motionAxisBeforePause = { ...state.taskHalStatus.ui.axisPose };
store.dispatch({ type: "PAUSE_RESUME" });
await waitForTaskHalCommand(store);
state = store.getState();
@@ -137,24 +137,24 @@ async function verifyRunFeedbackLoop({ profileId, sourceRel, stopAction }) {
assert.equal(state.machine.motionPaused, true);
assert.equal(state.machine.taskPaused, true);
assert.equal(state.taskHalStatus.ui.motionPaused, true);
assert.equal(state.taskHalStatus.motionStatus.motion.paused, true);
assert.equal(state.taskHalStatus.emcStatus.motion.paused, true);
assert.equal(state.programRuntimeFeedback.paused, true);
assert.equal(state.activeLine, activeLineBeforePause);
assert.equal(state.taskHalStatus.motionStatus.motion.programLine, motionLineBeforePause);
assert.deepEqual(state.taskHalStatus.motionStatus.axis, motionAxisBeforePause);
assert.equal(state.taskHalStatus.emcStatus.motion.programLine, motionLineBeforePause);
assert.deepEqual(state.taskHalStatus.ui.axisPose, motionAxisBeforePause);
await new Promise((resolve) => setTimeout(resolve, 120));
state = store.getState();
assert.equal(state.runState, "paused");
assert.equal(state.activeLine, activeLineBeforePause);
assert.equal(state.taskHalStatus.motionStatus.motion.programLine, motionLineBeforePause);
assert.deepEqual(state.taskHalStatus.motionStatus.axis, motionAxisBeforePause);
assert.equal(state.taskHalStatus.emcStatus.motion.programLine, motionLineBeforePause);
assert.deepEqual(state.taskHalStatus.ui.axisPose, motionAxisBeforePause);
store.dispatch({ type: "PAUSE_RESUME" });
await waitForTaskHalCommand(store);
await waitForState(store, (nextState) => nextState.machine.motionPaused === false && nextState.runState !== "paused");
state = store.getState();
assert.equal(state.machine.motionPaused, false);
assert.equal(state.taskHalStatus.motionStatus.motion.paused, false);
assert.equal(state.taskHalStatus.emcStatus.motion.paused, false);
const cycleAfterRun = state.taskHalStatus.ui.taskCycle;
store.dispatch({ type: "STEP" });

View File

@@ -119,6 +119,7 @@ assert.equal(fallbackSave.storageMode, "memory-fallback");
assert.equal(fallbackSave.storageCapability.opfsUnavailable, true);
const store = createSimulationStore();
store.dispatch({ type: "SET_PROFILE", profileId: "xyzac-trt" });
const machineStorage = createMemorySessionStorage();
await store.stageMachineFiles({ storage: machineStorage });
assert.equal(store.getState().toolDbReadiness.toolDbProcessReady, true);

View File

@@ -12,6 +12,7 @@ import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc
import {
buildTaskHalProgramMotionPlan,
buildTaskHalSessionFromMachineFiles,
normalizeTaskHalStatus,
wrapTaskHalSdk,
} from "../../app/src/runtime/linuxcnc-task-hal-runtime.js";
import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
@@ -199,9 +200,116 @@ const taskHalStatus = taskHal.readStatus();
assert.equal(taskHalStatus.summary.taskRuntimeReady, true);
assert.equal(taskHalStatus.summary.halSyncReady, true);
assert.equal(taskHalStatus.ui.activeLine >= 1, true);
assert.equal(Number.isFinite(taskHalStatus.motionStatus.axis.x), true);
assert.equal(Number.isFinite(taskHalStatus.motionStatus.axis.y), true);
assert.equal(Number.isFinite(taskHalStatus.motionStatus.axis.z), true);
assert.equal(taskHalStatus.statusSource, "StandaloneEmcStatus");
assert.equal(taskHalStatus.schemaVersion, 1);
assert.equal(taskHalStatus.emcStatus.source, "StandaloneEmcStatus");
assert.equal("task" in taskHalStatus, false);
assert.equal("motionStatus" in taskHalStatus, false);
assert.equal(taskHalStatus.ui.activeLine, taskHalStatus.emcStatus.task.currentLine);
assert.equal(taskHalStatus.ui.motionProgramLine, taskHalStatus.emcStatus.motion.programLine);
assert.equal(taskHalStatus.ui.motionQueueDepth, taskHalStatus.emcStatus.motion.traj.queue);
assert.equal(taskHalStatus.ui.axisPose.x, taskHalStatus.emcStatus.motion.axisByName.x);
assert.equal(Number.isFinite(taskHalStatus.ui.axisPose.x), true);
assert.equal(Number.isFinite(taskHalStatus.ui.axisPose.y), true);
assert.equal(Number.isFinite(taskHalStatus.ui.axisPose.z), true);
const emcOnlyTaskHalStatus = normalizeTaskHalStatus({
statusSource: "StandaloneEmcStatus",
schemaVersion: 1,
emcStatus: {
source: "StandaloneEmcStatus",
task: {
status: "EXEC",
state: "ON",
mode: "AUTO",
interpState: "READING",
execState: "EXEC",
cycle: 17,
programOpen: true,
planId: 9,
motionPlanLoaded: true,
allHomed: true,
currentLine: 12,
readLine: 12,
motionLine: 12,
callLevel: 1,
},
motion: {
valid: true,
status: "EXEC",
programLine: 12,
motionId: 44,
enabled: true,
commandQueueDepth: 2,
activeDepth: 1,
queueFull: false,
joint0: {
motorPosCmd: 10,
motorPosFb: 9.5,
},
traj: {
enabled: true,
inpos: false,
queue: 2,
activeQueue: 1,
queueFull: false,
id: 44,
paused: false,
singleStepping: false,
actualPosition: {
x: 10,
y: 20,
z: 30,
b: 40,
c: 50,
},
currentVel: 1.25,
},
axisByName: {
x: 10,
y: 20,
z: 30,
b: 40,
c: 50,
},
},
},
halSnapshot: {
ready: true,
pins: {
"motion.program-line": { value: 12 },
"motion.switchkins-type": { value: 1 },
},
},
});
assert.equal(emcOnlyTaskHalStatus.summary.taskRuntimeReady, true);
assert.equal(emcOnlyTaskHalStatus.summary.motionRuntimeReady, true);
assert.equal(emcOnlyTaskHalStatus.summary.halSyncReady, true);
assert.equal(emcOnlyTaskHalStatus.ui.taskMode, "auto");
assert.equal(emcOnlyTaskHalStatus.ui.interpState, "reading");
assert.equal(emcOnlyTaskHalStatus.ui.taskCycle, 17);
assert.equal(emcOnlyTaskHalStatus.ui.activeLine, 12);
assert.equal(emcOnlyTaskHalStatus.ui.motionQueueDepth, 2);
assert.equal(emcOnlyTaskHalStatus.ui.motionId, 44);
assert.equal(emcOnlyTaskHalStatus.ui.currentVelocity, 75);
assert.deepEqual(emcOnlyTaskHalStatus.ui.axisPose, {
x: 10,
y: 20,
z: 30,
a: 0,
b: 40,
c: 50,
});
assert.equal("task" in emcOnlyTaskHalStatus, false);
assert.equal("motionStatus" in emcOnlyTaskHalStatus, false);
assert.equal(emcOnlyTaskHalStatus.emcStatus.motion.joint0.motorPosFb, 9.5);
assert.throws(
() => normalizeTaskHalStatus({
task: { state: "ON", mode: "AUTO" },
motionStatus: { motion: { programLine: 1 }, axis: { x: 1 } },
}),
/removed legacy fields/,
);
const store = createSimulationStore();
const state = store.getState();
@@ -480,31 +588,65 @@ staleManualRunningStore.dispatch({
type: "TASK_HAL_STATUS_APPLIED",
operatorMessage: "simulated stale running task/HAL status",
status: {
taskRuntimeReady: true,
taskCommandsDriveMotionRuntime: true,
task: {
state: "ON",
mode: "AUTO",
interpState: "READING",
execState: "EXEC",
taskCycle: 123,
},
motionStatus: {
cycle: 25,
motionHalSyncReady: true,
statusSource: "StandaloneEmcStatus",
schemaVersion: 1,
emcStatus: {
source: "StandaloneEmcStatus",
task: {
state: "ON",
mode: "AUTO",
interpState: "READING",
execState: "EXEC",
cycle: 123,
taskCycle: 123,
programOpen: true,
openedLineCount: 999,
openedSourceLineCount: 999,
nextProgramLine: Number(lockedPauseState.activeLine || 1) + 5,
},
motion: {
valid: true,
programLine: Number(lockedPauseState.activeLine || 1) + 5,
currentVel: 12,
requestedVel: 12,
inPosition: false,
axisByName: {
x: 99,
y: 88,
z: 77,
b: 66,
c: 55,
},
traj: {
queue: 1,
actualPosition: {
x: 99,
y: 88,
z: 77,
b: 66,
c: 55,
},
},
},
axis: {
},
ui: {
taskState: "on",
taskMode: "auto",
interpState: "reading",
execState: "exec",
activeLine: Number(lockedPauseState.activeLine || 1) + 5,
motionProgramLine: Number(lockedPauseState.activeLine || 1) + 5,
activeLineSource: "emc-status",
taskCycle: 123,
servoCycle: 25,
axisPose: {
x: 99,
y: 88,
z: 77,
b: 66,
c: 55,
},
currentVelocity: 720,
},
halSnapshot: {
ready: true,
@@ -515,7 +657,7 @@ staleManualRunningStore.dispatch({
},
});
assert.equal(staleManualRunningStore.getState().runState, "running");
assert.equal(staleManualRunningStore.getState().machine.mode, "manual");
assert.equal(staleManualRunningStore.getState().machine.mode, "auto");
assert.equal(staleManualRunningStore.getState().machine.interpState, "reading");
assert.equal(staleManualRunningStore.getState().taskHalPauseLock, null);