148 lines
6.0 KiB
JavaScript
148 lines
6.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname, resolve } from "node:path";
|
|
|
|
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 { createSimulationStore } from "../../app/src/state/store.js";
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
const rootDir = resolve(__dirname, "../../..");
|
|
const wasmPath = resolve(rootDir, "wasm-port/build/wasm/task-hal/linuxcnc_task_hal.wasm");
|
|
|
|
const sdk = await createLinuxCncTaskHalSdk({
|
|
wasmBinary: readFileSync(wasmPath),
|
|
print() {},
|
|
printErr(message) {
|
|
console.error(message);
|
|
},
|
|
});
|
|
const runtime = wrapTaskHalSdk(sdk);
|
|
const profile = getFiveAxisProfile("xyzac-trt");
|
|
const iniText = readFileSync(
|
|
resolve(rootDir, "wasm-port/vendor/linuxcnc", profile.iniPath),
|
|
"utf8",
|
|
);
|
|
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" }),
|
|
});
|
|
store.dispatch({ type: "ATTACH_TASK_HAL_RUNTIME", runtime });
|
|
assert.equal(store.getState().taskHalRuntimeReadiness.taskRuntimeReady, true);
|
|
assert.equal(store.getState().taskHalRuntimeReadiness.motionRuntimeReady, 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" });
|
|
await waitForTaskHal(store);
|
|
store.dispatch({ type: "HOME" });
|
|
store.dispatch({ type: "SET_MODE", mode: "auto" });
|
|
await waitForTaskHal(store);
|
|
store.dispatch({ type: "RUN" });
|
|
await waitForTaskHal(store);
|
|
|
|
let state = store.getState();
|
|
assert.equal(state.taskHalStatus.summary.taskRuntimeReady, true);
|
|
assert.equal(state.taskHalStatus.summary.halSyncReady, true);
|
|
assert.equal(state.taskHalStatus.ui.activeLine >= 1, true);
|
|
assert.equal(state.programExecutionSourceMode, "linuxcnc-task-motion-hal-wasm");
|
|
assert.equal(state.fullExecutionBoundary.nativeTaskReady, 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" });
|
|
await waitForTaskHal(store);
|
|
store.dispatch({ type: "RUN_MDI", command: "M428" });
|
|
await waitForTaskHal(store);
|
|
state = store.getState();
|
|
assert.equal(state.taskHalStatus.ui.switchkinsType, 1);
|
|
assert.equal(state.kinsType, "tcp-xyzac");
|
|
assert.equal(state.rtcpState, "on");
|
|
|
|
store.dispatch({ type: "SET_MODE", mode: "manual" });
|
|
await waitForTaskHal(store);
|
|
store.dispatch({ type: "HOME" });
|
|
state = store.getState();
|
|
const homePose = { ...state.axisPose };
|
|
assert.equal(homePose.x, 43);
|
|
assert.equal(homePose.y, -32.15);
|
|
assert.equal(homePose.z, -11.306);
|
|
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.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");
|
|
assertNear(state.axisPose.z, homePose.z, "task/HAL JOG X+ should not reset Z");
|
|
|
|
store.dispatch({ type: "JOG", axis: "y", direction: -1, increment: 0.5 });
|
|
await waitForTaskHal(store);
|
|
state = store.getState();
|
|
assertNear(state.axisPose.x, homePose.x + 0.5, "task/HAL JOG Y- should not reset X");
|
|
assertNear(state.axisPose.y, homePose.y - 0.5, "task/HAL JOG Y- should keep HOME work-pose continuity");
|
|
assertNear(state.axisPose.z, homePose.z, "task/HAL JOG Y- should not reset Z");
|
|
|
|
store.dispatch({ type: "SET_MODE", mode: "auto" });
|
|
await waitForTaskHal(store);
|
|
store.dispatch({ type: "PAUSE" });
|
|
await waitForTaskHal(store);
|
|
assert.equal(store.getState().machine.interpState, "paused");
|
|
|
|
store.dispatch({ type: "RESUME" });
|
|
await waitForTaskHal(store);
|
|
assert.equal(store.getState().machine.interpState, "reading");
|
|
|
|
console.log("linuxcnc_task_hal_runtime_smoke=ok");
|
|
console.log("task_hal_machine_file_smoke=ok");
|
|
console.log("switchkins_remap_hal_sync_smoke=ok");
|
|
console.log("browser_task_hal_worker_smoke=ok");
|
|
|
|
async function waitForTaskHal(store) {
|
|
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
if (!store.getState().taskHalExecutionPending) {
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
if (!store.getState().taskHalExecutionPending) return store.getState();
|
|
}
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
}
|
|
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) {
|
|
assert.equal(Math.abs(Number(actual) - Number(expected)) < 1e-9, true, `${message}: ${actual} !== ${expected}`);
|
|
}
|