99 lines
7.2 KiB
JavaScript
99 lines
7.2 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile, mkdir, writeFile } from "node:fs/promises";
|
|
import createModule from "../../build-wasm/linuxcnc-sim-core.js";
|
|
import { loadSimulationCase, toWorkerConfiguration } from "../workspace/sim-case-loader.mjs";
|
|
import { readTrtConfiguration } from "../workspace/trt-configuration.mjs";
|
|
import { MACHINE_DEFINITIONS } from "../workspace/machine-definitions.mjs";
|
|
import { SimSession, CONTEXT_ID } from "./sim-session.mjs";
|
|
import { fnv1a64 } from "./replay.mjs";
|
|
|
|
const root = new URL("../../", import.meta.url);
|
|
const fetchImpl = async (url) => {
|
|
try { const source = await readFile(new URL(String(url).replace(/^\//, ""), root), "utf8"); return { ok: true, text: async () => source }; }
|
|
catch { return { ok: false }; }
|
|
};
|
|
const wasmBinary = await readFile(new URL("build-wasm/linuxcnc-sim-core.wasm", root));
|
|
function edit(bundle, values) {
|
|
let section = "";
|
|
const content = bundle.files[bundle.entrypoint].split("\n").map((line) => {
|
|
const heading = /^\s*\[([^\]]+)\]/.exec(line); if (heading) section = heading[1];
|
|
const assignment = /^\s*([A-Z_]+)\s*=/.exec(line);
|
|
const value = values[`${section}.${assignment?.[1]}`];
|
|
return value === undefined ? line : `${assignment[1]} = ${value}`;
|
|
}).join("\n");
|
|
return { ...bundle, files: { ...bundle.files, [bundle.entrypoint]: content } };
|
|
}
|
|
const rows = [];
|
|
for (const machine of MACHINE_DEFINITIONS) {
|
|
const original = await loadSimulationCase({ path: machine.entrypoint, fetchImpl });
|
|
const modified = edit(original, { "AXIS_X.MAX_VELOCITY": 4, "AXIS_X.MAX_ACCELERATION": 8, "AXIS_X.MAX_LIMIT": 15, "JOINT_0.MAX_LIMIT": 17, "TRAJ.MAX_LINEAR_VELOCITY": 13, "EMCMOT.SERVO_PERIOD": 2000000, "TASK.CYCLE_TIME": .02, "DISPLAY.MAX_FEED_OVERRIDE": 1.3 });
|
|
const config = toWorkerConfiguration(modified);
|
|
const zeroTolerance = { ...modified, files: { ...modified.files, [modified.entrypoint]: `${modified.files[modified.entrypoint]}\n[WEB_RUNTIME]\nSPINDLE_TOLERANCE = 0\n` } };
|
|
assert.equal(readTrtConfiguration(zeroTolerance).kinematics.io.spindle_tolerance, 0);
|
|
const blendingCase = { ...modified, files: { ...modified.files, [modified.entrypoint]: `${modified.files[modified.entrypoint]}\n[TRAJ]\nARC_BLEND_ENABLE = 1\nARC_BLEND_OPTIMIZATION_DEPTH = 25\nARC_BLEND_GAP_CYCLES = 3\nARC_BLEND_RAMP_FREQ = 50\nARC_BLEND_TANGENT_KINK_RATIO = 0.2\n` } };
|
|
const configuredBlend = readTrtConfiguration(blendingCase).kinematics.blend;
|
|
assert.equal(configuredBlend.optimization_depth, 25);
|
|
assert.equal(readTrtConfiguration(edit(original, { "TRAJ.MAX_LINEAR_ACCELERATION": 8 })).kinematics.max_acceleration, 8);
|
|
for (const declaration of ["SPINDLE_COUNT = 2.5", "TOOL_CAPACITY = 1002", "ENCODER_COUNT = 9"]) {
|
|
const invalid = { ...modified, files: { ...modified.files, [modified.entrypoint]: `${modified.files[modified.entrypoint]}\n[WEB_RUNTIME]\n${declaration}\n` } };
|
|
assert.throws(() => readTrtConfiguration(invalid), /trt_configuration_io_capacity/);
|
|
}
|
|
assert.deepEqual(config.home_limits.x, [-200, 17]);
|
|
assert.deepEqual(config.kinematics.axis_limits.x, [-200, 15]);
|
|
assert.equal(config.kinematics.axis_velocity_limits.x, 4);
|
|
for (const values of [{ "AXIS_X.MAX_VELOCITY": "NaN" }, { "AXIS_X.MAX_ACCELERATION": 0 }, { "JOINT_0.MAX_LIMIT": -300 }, { "KINS.JOINTS": 4 }, { "TASK.CYCLE_TIME": .0015 }, { "DISPLAY.MAX_FEED_OVERRIDE": .5 }]) assert.throws(() => readTrtConfiguration(edit(original, values)), /trt_configuration/);
|
|
const core = await createModule({ wasmBinary });
|
|
const session = new SimSession(core, { sessionId: `configuration-${machine.id}` }); let seq = 0;
|
|
const send = (kind, payload = {}, expected = "ok") => {
|
|
const result = session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId, kind, seq: ++seq, operation_id: `cfg-${seq}`, payload });
|
|
if (expected === "error") assert.equal(result.kind, "ERROR");
|
|
else assert.notEqual(result.kind, "ERROR", `${kind}: ${JSON.stringify(result.payload)}`);
|
|
return result;
|
|
};
|
|
send("HELLO"); send("SESSION_ATTACH");
|
|
send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: toWorkerConfiguration(original) });
|
|
// Existing adapter values are untrusted duplicates: the files must win.
|
|
config.home_limits.x = [-1, 1]; config.kinematics.max_linear_velocity = 99;
|
|
send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: config });
|
|
assert.equal(session.wasmMotion.velocityLimit, 13);
|
|
assert.equal(session.wasmMotion.cycleTime, .002);
|
|
assert.equal(session.wasmHomeLimit.period, .002);
|
|
assert.equal(session.xyzacTaskCycleTicks, 10);
|
|
assert.equal(readTrtConfiguration(modified).kinematics.io.cycle_time, .02);
|
|
assert.equal(session.wasmMotion.jogVelocity("x"), 4);
|
|
assert.deepEqual(session.xyzacKinematics.snapshot().limits.x, [-200, 17]);
|
|
send("POWER", { active: true }); send("HOME", { all: true });
|
|
for (let i = 0; i < 500 && !session.wasmHomeLimit.read().state.homed; i++) send("SIM_STEP", { cycles: 1 });
|
|
assert.ok(session.wasmHomeLimit.read().state.homed);
|
|
send("SET_OVERRIDE", { feed: 1.31 }, "error"); send("SET_OVERRIDE", { feed: 1.3 }); send("SET_OVERRIDE", { feed: 1 });
|
|
send("JOG_START", { axis: "x", direction: 1, increment: 16 }, "error");
|
|
send("MDI_EXECUTE", { block: "G21 G90 G1 X10 F1200" });
|
|
let maxVelocity = 0, maxAcceleration = 0, previousVelocity = 0;
|
|
const trace = [];
|
|
for (let i = 0; i < 2000 && session.wasmMotion.read().state.queue_depth > 0; i++) {
|
|
send("SIM_STEP", { cycles: 1 });
|
|
const state = session.wasmMotion.read().state;
|
|
maxVelocity = Math.max(maxVelocity, state.velocity);
|
|
maxAcceleration = Math.max(maxAcceleration, Math.abs(state.velocity - previousVelocity) / .02);
|
|
previousVelocity = state.velocity;
|
|
trace.push({ tick: session.tick, x: state.position.x, velocity: state.velocity });
|
|
}
|
|
assert.equal(session.wasmMotion.read().state.queue_depth, 0);
|
|
assert.ok(maxVelocity > 3.9 && maxVelocity <= 4.000001, `${machine.id} velocity ${maxVelocity}`);
|
|
assert.ok(maxAcceleration <= 8.000001, `${machine.id} acceleration ${maxAcceleration}`);
|
|
assert.ok(Math.abs(session.wasmMotion.read().state.position.x - 10) < 1e-8);
|
|
rows.push({ machine: machine.id, max_velocity: maxVelocity, max_acceleration: maxAcceleration, period: session.wasmMotion.cycleTime, joint_limit: session.xyzacKinematics.snapshot().limits.x, trace });
|
|
const checkpoint = session.createCheckpoint();
|
|
send("POWER", { active: false });
|
|
const newSpeed = toWorkerConfiguration(edit(modified, { "AXIS_X.MAX_VELOCITY": 5 }));
|
|
send("LOAD_WORKSPACE", { root_path: "/workspace", configuration: newSpeed });
|
|
const restored = session.restoreCheckpoint(checkpoint);
|
|
assert.equal(restored.status, "blocked");
|
|
assert.equal(restored.blocked[0].reason, "trt_recovery_configuration_mismatch");
|
|
assert.equal(session.wasmMotion.jogVelocity("x"), 5);
|
|
}
|
|
const report = { report_id: "trt-configuration-v1", status: "verified", rows };
|
|
await mkdir(new URL("coverage/configuration/", root), { recursive: true });
|
|
await writeFile(new URL("coverage/configuration/trt-runtime.report.json", root), JSON.stringify({ ...report, state_hash: fnv1a64(report) }, null, 2) + "\n");
|
|
console.log(JSON.stringify({ status: report.status, report_id: report.report_id, rows: rows.map(({ trace, ...row }) => row) }));
|