87 lines
5.3 KiB
JavaScript
87 lines
5.3 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
|
|
import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs";
|
|
import { fnv1a64 } from "./replay.mjs";
|
|
import { CONTEXT_ID, SimSession } from "./sim-session.mjs";
|
|
|
|
const CASE = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini";
|
|
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 programs = [
|
|
{ name: "program-a.ngc", source: "G21\nG90\nG0 X0 Y0 Z10 A0 C0\nG1 X10 Y0 Z10 A0 C0 F600\nM2\n", expected: { x: 10, y: 0, z: 10 } },
|
|
{ name: "program-b.ngc", source: "G21\nG91\nG1 X5 F600\nG1 Y4 F600\nM2\n", delta: { x: 5, y: 4, z: 0 } },
|
|
{ name: "program-c.ngc", source: "G21\nG91\nG1 X-3 Y-2 Z2 F600\nM2\n", delta: { x: -3, y: -2, z: 2 } },
|
|
];
|
|
|
|
const bundle = await loadSimulationCase({ path: CASE, fetchImpl });
|
|
assert.equal(bundle.status, "ready");
|
|
const wasmBinary = await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url));
|
|
const core = await createModule({ wasmBinary });
|
|
const session = new SimSession(core, { sessionId: "xyzac-multi-program-load" });
|
|
const base = { schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId };
|
|
let seq = 0;
|
|
const send = (kind, payload = {}) => session.accept({ ...base, kind, seq: ++seq, operation_id: `${kind.toLowerCase()}-${seq}`, payload });
|
|
const close = (actual, expected, label) => assert.ok(Math.abs(Number(actual) - Number(expected)) <= 1e-7, `${label}: ${actual} != ${expected}`);
|
|
const runToCompletion = () => {
|
|
const started = send("PROGRAM_RUN");
|
|
assert.equal(started.kind, "COMMAND_RESULT", JSON.stringify(started));
|
|
let response = started;
|
|
for (let index = 0; index < 2000; index += 1) {
|
|
response = send("SIM_STEP", { cycles: 20, response: "motion_delta" });
|
|
assert.equal(response.kind, "COMMAND_RESULT", JSON.stringify(response));
|
|
if (response.payload.program?.program_status === "completed" && Number(response.payload.wasm_motion?.state?.queue_depth ?? -1) === 0) return response;
|
|
}
|
|
throw new Error(`program_completion_timeout:${JSON.stringify(response.payload?.program)}`);
|
|
};
|
|
|
|
send("HELLO");
|
|
send("SESSION_ATTACH");
|
|
assert.equal(send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(bundle) }).payload.configuration.state, "TASK_READY");
|
|
assert.equal(send("POWER", { active: true }).payload.status, "verified");
|
|
assert.equal(send("HOME", { all: true }).payload.status, "verified");
|
|
|
|
const evidence = [];
|
|
let expectedPose = { x: 0, y: 0, z: 0 };
|
|
for (const program of programs) {
|
|
const before = session.wasmMotion.read().state.position;
|
|
close(before.x, expectedPose.x, `${program.name}:before.x`);
|
|
close(before.y, expectedPose.y, `${program.name}:before.y`);
|
|
close(before.z, expectedPose.z, `${program.name}:before.z`);
|
|
const loaded = send("PROGRAM_LOAD", { source: program.source, path: `browser/${program.name}` });
|
|
assert.equal(loaded.kind, "COMMAND_RESULT", JSON.stringify(loaded));
|
|
assert.equal(loaded.payload.program.source_hash, fnv1a64(program.source));
|
|
assert.equal(loaded.payload.program.current_line, 0);
|
|
const firstMotion = loaded.payload.preview.segments[0];
|
|
close(firstMotion.start.x, before.x, `${program.name}:preview.start.x`);
|
|
close(firstMotion.start.y, before.y, `${program.name}:preview.start.y`);
|
|
close(firstMotion.start.z, before.z, `${program.name}:preview.start.z`);
|
|
const completed = runToCompletion();
|
|
expectedPose = program.expected ?? {
|
|
x: before.x + program.delta.x,
|
|
y: before.y + program.delta.y,
|
|
z: before.z + program.delta.z,
|
|
};
|
|
close(completed.payload.wasm_motion.state.position.x, expectedPose.x, `${program.name}:final.x`);
|
|
close(completed.payload.wasm_motion.state.position.y, expectedPose.y, `${program.name}:final.y`);
|
|
close(completed.payload.wasm_motion.state.position.z, expectedPose.z, `${program.name}:final.z`);
|
|
evidence.push({ name: program.name, source_hash: loaded.payload.program.source_hash, preview_segments: loaded.payload.preview.segments.length, synchronized_start: { x: firstMotion.start.x, y: firstMotion.start.y, z: firstMotion.start.z }, final: expectedPose });
|
|
}
|
|
|
|
const beforeRoundTrip = session.wasmMotion.read().state.position;
|
|
const roundTrip = send("PROGRAM_LOAD", { source: programs[0].source, path: `browser/${programs[0].name}` });
|
|
assert.equal(roundTrip.payload.program.source_hash, evidence[0].source_hash);
|
|
assert.notEqual(roundTrip.payload.program.source_hash, evidence[1].source_hash);
|
|
assert.notEqual(roundTrip.payload.program.source_hash, evidence[2].source_hash);
|
|
close(roundTrip.payload.preview.segments[0].start.x, beforeRoundTrip.x, "roundtrip:start.x");
|
|
close(roundTrip.payload.preview.segments[0].start.y, beforeRoundTrip.y, "roundtrip:start.y");
|
|
close(roundTrip.payload.preview.segments[0].start.z, beforeRoundTrip.z, "roundtrip:start.z");
|
|
|
|
console.log(JSON.stringify({ schema: "xyzac-multi-program-load.v1", status: "verified", source: "axis.py:open_file_guts/task_plan_synch", programs: evidence, round_trip_hash: roundTrip.payload.program.source_hash }));
|