118 lines
7.8 KiB
JavaScript
118 lines
7.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
|
|
import { fnv1a64 } from "./replay.mjs";
|
|
import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs";
|
|
import { SimSession, CONTEXT_ID } from "./sim-session.mjs";
|
|
|
|
export const SELECTED_CASE = "linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.ini";
|
|
const EXPECTED_FILES = [
|
|
SELECTED_CASE,
|
|
"linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial.hal",
|
|
"linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial",
|
|
"linuxcnc/configs/sim/axis/vismach/3axis-tutorial/3axis-tutorial_mm.var",
|
|
"linuxcnc/configs/nc_files/3dtest.ngc",
|
|
"linuxcnc/configs/sim/axis/vismach/3axis-tutorial/tool.tbl",
|
|
"linuxcnc/linuxcnc.gif",
|
|
];
|
|
|
|
const fetchImpl = async (url) => {
|
|
try {
|
|
const body = await readFile(new URL(`../../${String(url).replace(/^\//, "")}`, import.meta.url));
|
|
return { ok: true, text: async () => body.toString("utf8") };
|
|
} catch {
|
|
return { ok: false, text: async () => "" };
|
|
}
|
|
};
|
|
|
|
const bundle = await loadSimulationCase({ path: SELECTED_CASE, fetchImpl });
|
|
assert.equal(bundle.status, "ready");
|
|
assert.deepEqual(bundle.missing, []);
|
|
assert.deepEqual(bundle.optional_missing, []);
|
|
assert.equal(bundle.catalog.path, SELECTED_CASE);
|
|
assert.equal(bundle.catalog.coordinates, "X Y Z");
|
|
assert.ok(bundle.files["linuxcnc/configs/nc_files/3dtest.ngc"].includes("start xy circle"));
|
|
for (const path of EXPECTED_FILES) assert.ok(Object.hasOwn(bundle.files, path), `missing exact case file: ${path}`);
|
|
assert.equal(bundle.files["linuxcnc/configs/sim/axis/vismach/3axis-tutorial/tool.tbl"].includes("T1"), true);
|
|
|
|
const wasm = await createModule({ wasmBinary: await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)) });
|
|
const session = new SimSession(wasm, { sessionId: "axis3-selected-case" });
|
|
const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId };
|
|
let sequence = 0;
|
|
const send = (kind, operationId, payload = {}, expected = "ok") => {
|
|
const response = session.accept({ ...base, kind, seq: ++sequence, operation_id: operationId, payload });
|
|
if (expected === "error") assert.equal(response.kind, "ERROR");
|
|
else assert.notEqual(response.kind, "ERROR", `${kind}:${response.payload?.code ?? "error"}`);
|
|
return response;
|
|
};
|
|
|
|
assert.equal(send("HELLO", "hello").kind, "CAPABILITIES");
|
|
assert.equal(send("SESSION_ATTACH", "attach").kind, "SESSION_READY");
|
|
const loaded = send("LOAD_WORKSPACE", "load", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) });
|
|
assert.equal(loaded.payload.configuration.state, "TASK_READY");
|
|
assert.equal(loaded.payload.configuration.hal.blocked_count, 0);
|
|
assert.ok(loaded.payload.configuration.hal.records.some((record) => record.program === "./3axis-tutorial" && record.capability === "WEB-EQUIV"));
|
|
|
|
assert.equal(send("HOME", "home-z", { axis: "z" }).payload.status, "verified");
|
|
assert.equal(send("HOME", "home-x", { axis: "x" }).payload.status, "verified");
|
|
assert.equal(send("HOME", "home-y", { axis: "y" }).payload.status, "verified");
|
|
send("JOG_START", "jog-x", { axis: "x", direction: 1, speed: 2 });
|
|
const jog = send("SIM_STEP", "jog-step", { cycles: 10 });
|
|
assert.ok(jog.payload.motion.axes.x > 0);
|
|
assert.equal(send("JOG_STOP", "jog-stop").payload.status, "verified");
|
|
assert.equal(send("SET_OVERRIDE", "override", { feed: 0.5, spindle: 1.2 }).payload.state.feed_override, 0.5);
|
|
|
|
send("PROGRAM_LOAD", "program-load", { path: "axis3.ngc", source: "G21 G90\nG0 X0 Y0 Z0\nG1 X10 Y5 F120\nG1 Z-1\nM2\n" });
|
|
assert.equal(send("PROGRAM_RUN", "program-run").payload.task.state, "RUNNING");
|
|
const checkpoint = send("CHECKPOINT", "checkpoint").payload.checkpoint;
|
|
send("SIM_STEP", "program-step", { cycles: 8 });
|
|
const programSnapshot = send("SNAPSHOT_REQUEST", "program-snapshot");
|
|
assert.ok(["running", "completed"].includes(programSnapshot.payload.snapshot?.state?.program?.program_status ?? "running"));
|
|
assert.equal(send("SPINDLE_COMMAND", "spindle-during-motion", { spindle: 0, rpm: 600, enabled: true }, "error").payload.code, "axis_gate");
|
|
assert.equal(send("TOOL_PREPARE", "tool-during-motion", { tool: 1, pocket: 1 }, "error").payload.code, "axis_gate");
|
|
for (let tick = 0; tick < 200 && (session.program.snapshot().program_status !== "completed" || session.wasmMotion.read().state.queue_depth > 0); tick++) send("SIM_STEP", `finish-program-${tick}`, { cycles: 100 });
|
|
assert.equal(session.program.snapshot().program_status, "completed");
|
|
assert.equal(session.wasmMotion.read().state.queue_depth, 0);
|
|
send("SET_MODE", "manual-after-program", { mode: "MANUAL" });
|
|
|
|
assert.equal(send("SPINDLE_COMMAND", "spindle-on", { spindle: 0, rpm: 600, enabled: true }).payload.status, "verified");
|
|
assert.equal(send("IO_STEP", "spindle-step", { ticks: 5 }).payload.status, "verified");
|
|
assert.equal(send("SPINDLE_COMMAND", "spindle-off", { spindle: 0, rpm: 0, enabled: false }).payload.status, "verified");
|
|
assert.equal(send("IO_STEP", "spindle-stop-step", { ticks: 10 }).payload.status, "verified");
|
|
assert.equal(send("COOLANT_COMMAND", "coolant", { mist: true, flood: true, lube: false }).payload.status, "verified");
|
|
assert.equal(send("TOOL_PREPARE", "tool-prepare", { tool: 1, pocket: 1 }).payload.status, "verified");
|
|
assert.equal(send("TOOL_PREPARED", "tool-prepared", { prepared: true }).payload.status, "verified");
|
|
assert.equal(send("TOOL_CHANGE", "tool-change").payload.status, "verified");
|
|
assert.equal(send("TOOL_CHANGED", "tool-changed", { changed: true }).payload.status, "verified");
|
|
const safetyCore = await createModule({ wasmBinary: await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url)) });
|
|
const safetySession = new SimSession(safetyCore, { sessionId: "axis3-selected-case-safety" });
|
|
const safetyBase = { schema_version: 1, context_id: CONTEXT_ID, session_id: safetySession.sessionId };
|
|
let safetySequence = 0;
|
|
const safetySend = (kind, operationId, payload = {}) => safetySession.accept({ ...safetyBase, kind, seq: ++safetySequence, operation_id: operationId, payload });
|
|
safetySend("HELLO", "hello");
|
|
safetySend("SESSION_ATTACH", "attach");
|
|
safetySend("LOAD_WORKSPACE", "load", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) });
|
|
safetySend("HOME", "home-z", { axis: "z" });
|
|
safetySend("HOME", "home-x", { axis: "x" });
|
|
safetySend("HOME", "home-y", { axis: "y" });
|
|
const estop = safetySend("ESTOP", "estop", { active: true });
|
|
assert.equal(estop.kind, "ERROR");
|
|
assert.equal(estop.payload.code, "sim_io_estop");
|
|
assert.equal(safetySend("ESTOP", "estop-clear", { active: false }).payload.status, "verified");
|
|
assert.equal(safetySend("IO_RECOVER", "io-recover").payload.status, "verified");
|
|
const recovered = session.restoreCheckpoint(checkpoint, session.timeline.export(), (operation) => operation);
|
|
assert.equal(recovered.status, "recovered");
|
|
|
|
const result = {
|
|
schema: "axis3-selected-case-parity.v1",
|
|
status: "verified",
|
|
case: SELECTED_CASE,
|
|
machine: { type: "cartesian_vismach", axes: ["X", "Y", "Z"], joints: 3, kinematics: "trivkins", model: "3axis-tutorial" },
|
|
exact_files: EXPECTED_FILES,
|
|
loader: { fetched: bundle.fetched.length, missing: bundle.missing.length, state_hash: bundle.state_hash },
|
|
startup: { state: loaded.payload.configuration.state, hal_blocked: loaded.payload.configuration.hal.blocked_count },
|
|
operation_sequence: ["HOME", "JOG_START", "SIM_STEP", "JOG_STOP", "SET_OVERRIDE", "PROGRAM_LOAD", "PROGRAM_RUN", "CHECKPOINT", "SPINDLE_COMMAND", "COOLANT_COMMAND", "TOOL_PREPARE", "TOOL_CHANGE", "ESTOP", "IO_RECOVER", "RECOVER"],
|
|
observed: { jog_x: jog.payload.motion.axes.x, program_status: programSnapshot.payload.snapshot?.state?.program?.program_status ?? null, checkpoint_hash: checkpoint.checkpoint_hash, recovery: recovered.status, final_hash: fnv1a64(session.workspacePackageState()) },
|
|
};
|
|
console.log(JSON.stringify(result));
|