111 lines
6.8 KiB
JavaScript
111 lines
6.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import { createHash } from "node:crypto";
|
|
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
|
|
import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs";
|
|
import { XyzacSession, CONTEXT_ID } from "./xyzac-session.mjs";
|
|
|
|
const path = "linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini";
|
|
const bundle = await loadSimulationCase({ path, fetchImpl: async (url) => {
|
|
try { const body = await readFile(new URL(`../../${String(url).replace(/^\//, "")}`, import.meta.url), "utf8"); return { ok: true, text: async () => body }; }
|
|
catch { return { ok: false, text: async () => "" }; }
|
|
} });
|
|
const binary = await readFile(new URL("../../build-wasm/linuxcnc-sim-core.wasm", import.meta.url));
|
|
for (const [file, hash] of [["homing.c", "ee73d38f2feb8d5523b9a733cb17d635d3904667aaa219f8a02d7fbffee1ba39"], ["simple_tp.c", "5a0026f4d5bdd9d1bc55290c6ea9c399abd664633b69413c307f7353953d2603"]]) {
|
|
assert.equal(createHash("sha256").update(await readFile(new URL(`../../linuxcnc/src/emc/motion/${file}`, import.meta.url))).digest("hex"), hash, `source drift: ${file}`);
|
|
}
|
|
async function setup(id, finalHome = 0) {
|
|
const core = await createModule({ wasmBinary: binary });
|
|
const session = new XyzacSession(core, { sessionId: id });
|
|
let seq = 0;
|
|
const send = (kind, payload = {}) => session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: id, seq: ++seq, operation_id: `${id}-${seq}`, kind, payload });
|
|
const ok = (kind, payload) => { const r = send(kind, payload); assert.notEqual(r.kind, "ERROR", `${kind}: ${r.payload.message_key}`); return r.payload; };
|
|
ok("HELLO"); ok("SESSION_ATTACH");
|
|
const configuration = toWorkerConfiguration(bundle);
|
|
if (finalHome) configuration.files[path] = configuration.files[path].replace(/HOME =\s+0/g, `HOME = ${finalHome}`);
|
|
ok("LOAD_WORKSPACE", { root_path: "/workspace", configuration });
|
|
ok("PROGRAM_LOAD", { source: "G21 G90\nG1 X10 F600\nM2\n", path: "native-home.ngc" });
|
|
ok("POWER", { active: true });
|
|
const snapshot = () => ok("SNAPSHOT_REQUEST").panel_snapshot;
|
|
const drainHome = () => {
|
|
let ticks = 0;
|
|
while (session.wasmHomeLimit.active && ticks++ < 1000) ok("SIM_STEP", { cycles: 1, response: "motion_delta" });
|
|
assert.equal(session.wasmHomeLimit.active, false);
|
|
return snapshot();
|
|
};
|
|
const jog = (axis, increment) => {
|
|
ok("JOG_START", { axis, direction: 1, increment });
|
|
let ticks = 0;
|
|
while (session.wasmMotion.read().state.queue_depth > 0 && ticks++ < 5000) ok("SIM_STEP", { cycles: 1, response: "motion_delta" });
|
|
assert.equal(session.wasmMotion.read().state.queue_depth, 0);
|
|
};
|
|
return { session, send, ok, snapshot, drainHome, jog };
|
|
}
|
|
|
|
const s = await setup("native-homing");
|
|
assert.equal(s.snapshot().source_status.traj.mode, "FREE");
|
|
assert.equal(s.send("HOME", { axis: "x" }).payload.message_key, "home_joint_requires_joint_mode");
|
|
for (const joint of [0, 1, 2, 3, 4]) {
|
|
const ack = s.ok("HOME", { joint });
|
|
assert.equal(ack.homing.phase, "accepted");
|
|
assert.equal(ack.panel_snapshot.source_status.homing_active, true);
|
|
assert.equal(ack.panel_snapshot.source_status.task.interpState, "INTERP_IDLE");
|
|
assert.equal(ack.panel_snapshot.axis_state.operation_state, "HOME");
|
|
assert.equal(ack.panel_snapshot.motion.homed_axes[joint], false);
|
|
const done = s.drainHome();
|
|
assert.equal(done.motion.homed_axes[joint], true);
|
|
assert.equal(done.source_status.traj.mode, joint === 4 ? "TELEOP" : "FREE");
|
|
assert.ok(done.source_status.homing_history.some((e) => e.joint[joint].home_state === 21 && e.joint[joint].homing));
|
|
}
|
|
for (const [joint, axis] of ["x", "y", "z", "a", "c"].entries()) {
|
|
s.jog(axis, 3);
|
|
const before = s.snapshot();
|
|
s.ok("HOME", { joint });
|
|
const after = s.drainHome();
|
|
after.source_status.joint.forEach((j, index) => {
|
|
assert.ok(Math.abs(j.motor_position - before.source_status.joint[index].motor_position) < 1e-8, "no-search home does not move a motor");
|
|
assert.ok(Math.abs(j.position - (index === joint ? 0 : before.source_status.joint[index].position)) < 1e-8);
|
|
});
|
|
assert.equal(after.visual.live_path_epoch, before.visual.live_path_epoch);
|
|
}
|
|
s.jog("x", 4); s.jog("y", 2);
|
|
const beforeAll = s.snapshot();
|
|
s.ok("HOME", { all: true });
|
|
for (const [kind, payload] of [["PROGRAM_RUN", {}], ["MDI_EXECUTE", { block: "G0 X1" }], ["SET_MODE", { mode: "AUTO" }]]) assert.equal(s.send(kind, payload).kind, "ERROR");
|
|
const afterAll = s.drainHome();
|
|
assert.deepEqual(afterAll.source_status.joint.map((j) => j.position), [0, 0, 0, 0, 0]);
|
|
assert.deepEqual(afterAll.source_status.joint.map((j) => j.motor_position), beforeAll.source_status.joint.map((j) => j.motor_position));
|
|
const nativeCheckpoint = s.session.wasmHomeLimit.exportState();
|
|
s.session.wasmHomeLimit.reset();
|
|
s.session.wasmHomeLimit.restoreState(nativeCheckpoint);
|
|
assert.deepEqual(s.session.wasmHomeLimit.read().state.joint, afterAll.source_status.joint);
|
|
s.ok("PROGRAM_RUN");
|
|
assert.equal(s.snapshot().source_status.task.mode, "AUTO");
|
|
assert.equal(s.snapshot().source_status.traj.mode, "COORD");
|
|
s.ok("PROGRAM_PAUSE");
|
|
assert.equal(s.snapshot().source_status.task.interpState, "INTERP_PAUSED");
|
|
assert.equal(s.send("SET_MODE", { mode: "MANUAL" }).kind, "ERROR");
|
|
s.ok("PROGRAM_ABORT"); s.ok("SET_MODE", { mode: "MANUAL" });
|
|
assert.equal(s.snapshot().source_status.traj.mode, "TELEOP");
|
|
s.ok("HOME", { joint: 0 }); s.ok("ESTOP", { active: true }); s.ok("SIM_STEP", { cycles: 1 });
|
|
assert.equal(s.snapshot().source_status.homing_active, false);
|
|
assert.equal(s.snapshot().source_status.joint[0].homed, false);
|
|
s.ok("ESTOP", { active: false }); s.ok("POWER", { active: true }); s.ok("HOME", { joint: 0 }); s.drainHome();
|
|
|
|
// A configured final move uses the original joint free_tp, not coordinated TP.
|
|
const moving = await setup("native-final-move", 5);
|
|
moving.ok("HOME", { joint: 0 });
|
|
const mid = moving.ok("SIM_STEP", { cycles: 1 }).panel_snapshot;
|
|
assert.equal(mid.source_status.joint[0].home_state, 21);
|
|
assert.ok(mid.source_status.joint[0].position > 0 && mid.source_status.joint[0].position < 5);
|
|
assert.equal(mid.wasm_motion.state.queue_depth, 0);
|
|
assert.equal(mid.source_status.joint[0].homed, false);
|
|
const done = moving.drainHome();
|
|
assert.ok(Math.abs(done.source_status.joint[0].position - 5) < 1e-8);
|
|
assert.ok(Math.abs(done.source_status.joint[0].motor_position - 5) < 1e-8);
|
|
moving.ok("HOME", { joint: 1 }); moving.ok("SIM_STEP", { cycles: 1 }); moving.ok("PROGRAM_ABORT");
|
|
moving.drainHome();
|
|
assert.equal(moving.snapshot().source_status.joint[1].homed, false);
|
|
assert.equal(moving.snapshot().source_status.homing_active, false);
|
|
console.log(JSON.stringify({ status: "verified", source: "homing.c + simple_tp.c", checks: ["five-joint-home", "no-search-coordinate-rebase", "motor-position-preserved", "native-final-move", "mode-interlocks", "abort-and-recovery", "servo-transition-history"] }));
|