Make machine configuration authoritative and restore native checkpoints exactly
This commit is contained in:
98
web/worker/test_trt_configuration.mjs
Normal file
98
web/worker/test_trt_configuration.mjs
Normal file
@@ -0,0 +1,98 @@
|
||||
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) }));
|
||||
Reference in New Issue
Block a user