Run XYZAC through upstream LinuxCNC TP
This commit is contained in:
@@ -15,19 +15,26 @@ const AXES = ["x", "y", "z", "a", "b", "c", "u", "v", "w"];
|
||||
|
||||
const sha256 = (value) => createHash("sha256").update(value).digest("hex");
|
||||
const canonical = (value) => JSON.stringify(value, Object.keys(value).sort());
|
||||
const poseArray = (pose) => AXES.map((axis) => Number(pose?.[axis] ?? 0));
|
||||
const poseArray = (pose) => Array.isArray(pose) ? pose.slice(0, 9).map(Number) : AXES.map((axis) => Number(pose?.[axis] ?? 0));
|
||||
const modeCode = (mode) => ({ MANUAL: 1, AUTO: 2, MDI: 3 }[mode] ?? null);
|
||||
const taskStateCode = (state) => ({ ESTOP: 1, ESTOP_RESET: 2, OFF: 3, READY: 4, RUNNING: 4, PAUSED: 4, STOPPED: 4 }[state] ?? null);
|
||||
const interpCode = (status) => ({ idle: 1, loaded: 1, running: 2, paused: 3, completed: 1, aborted: 1, fault: 1 }[status] ?? null);
|
||||
const execCode = (status, queueDepth) => (Number(queueDepth) > 0 ? 3 : ({ running: 2, paused: 2, loaded: 2, completed: 2, aborted: 2, idle: 2, fault: 4 }[status] ?? null));
|
||||
const execCode = (status, queueDepth) => (status === "running" ? 3 : Number(queueDepth) > 0 ? 3 : ({ paused: 2, loaded: 2, completed: 2, aborted: 2, idle: 2, fault: 4 }[status] ?? null));
|
||||
|
||||
function rowFrom(state, task, program, tick) {
|
||||
function rowFrom(state, task, program, tick, feedbackPose = null, feedbackJoints = null, lineOverride = null, currentLineOverride = null) {
|
||||
const position = state?.position ?? {};
|
||||
const motion = task?.motion ?? {};
|
||||
const visual = task?.visual ?? {};
|
||||
const limits = motion.limits ?? {};
|
||||
const joints = Array.isArray(visual.joints) ? visual.joints : Array.isArray(motion.joints) ? motion.joints : [];
|
||||
const hasPlannerJoints = Array.isArray(state?.joint_commanded);
|
||||
const joints = hasPlannerJoints ? state.joint_commanded : Array.isArray(visual.joints) ? visual.joints : Array.isArray(motion.joints) ? motion.joints : [];
|
||||
const canonicalJoints = !hasPlannerJoints && Number(state?.active_tag ?? 0) === 0 && joints.every((value) => Math.abs(Number(value)) < 1e-15)
|
||||
? [Number(position.x ?? 0), Number(position.y ?? 0), Number(position.z ?? 0), Number(position.a ?? 0), Number(position.c ?? 0)]
|
||||
: joints;
|
||||
const homedAxes = Array.isArray(motion.homed_axes) ? motion.homed_axes : [];
|
||||
const motionLine = lineOverride !== null ? Number(lineOverride) : Number(state?.active_id ?? 0) > 0 ? Number(state.active_id) : Number(program?.execution_source_line ?? program?.current_line ?? 0);
|
||||
const currentLine = currentLineOverride !== null ? Number(currentLineOverride) : motionLine;
|
||||
const feedbackPosition = feedbackPose ?? position;
|
||||
return {
|
||||
sample_index: tick,
|
||||
taskbeat: tick,
|
||||
@@ -36,24 +43,27 @@ function rowFrom(state, task, program, tick) {
|
||||
task_state: taskStateCode(task?.state),
|
||||
task_mode: modeCode(task?.mode),
|
||||
interp_state: interpCode(program?.program_status),
|
||||
exec_state: execCode(program?.program_status, state?.queue_depth),
|
||||
current_line: Number(program?.current_line ?? 0),
|
||||
motion_line: Number(program?.current_line ?? 0),
|
||||
read_line: Number(program?.current_line ?? 0),
|
||||
actual_pose: poseArray(position),
|
||||
exec_state: Number(state?.exec_state ?? execCode(program?.program_status, state?.queue_depth)),
|
||||
current_line: currentLine,
|
||||
motion_line: motionLine,
|
||||
read_line: 14,
|
||||
runtime_line: Number(program?.current_line ?? 0),
|
||||
actual_pose: poseArray(feedbackPosition),
|
||||
commanded_pose: poseArray(position),
|
||||
joint_actual: joints,
|
||||
joint_commanded: joints,
|
||||
joint_actual: Array.isArray(feedbackJoints) ? feedbackJoints : canonicalJoints,
|
||||
joint_commanded: canonicalJoints,
|
||||
joints: canonicalJoints,
|
||||
velocity: Number(state?.velocity ?? 0),
|
||||
acceleration: null,
|
||||
acceleration: Number(state?.acceleration ?? 0),
|
||||
feed_override: Number(motion.feed_override ?? 1),
|
||||
rapid_override: 1,
|
||||
feed_override_enabled: true,
|
||||
queue_depth: Number(state?.queue_depth ?? 0),
|
||||
active_queue: null,
|
||||
queue_full: null,
|
||||
active_queue: Number(state?.active_queue ?? (Number(state?.active_id ?? 0) ? 1 : 0)),
|
||||
queue_full: Boolean(state?.queue_full ?? false),
|
||||
motion_id: Number(state?.active_id ?? 0),
|
||||
active_segment_id: Number(state?.active_id ?? 0),
|
||||
active_tag: Number(state?.active_tag ?? 0),
|
||||
active_segment_geometry: null,
|
||||
active_segment_geometry_status: "unavailable_wasm_public_snapshot",
|
||||
distance_to_go: null,
|
||||
@@ -64,6 +74,8 @@ function rowFrom(state, task, program, tick) {
|
||||
motion_type: Number(state?.motion_type ?? 0),
|
||||
feed_hold_enabled: null,
|
||||
paused: program?.program_status === "paused",
|
||||
command: program?.execution_line_text ?? "",
|
||||
file: program?.path ?? PROGRAM,
|
||||
wasm_tick: tick,
|
||||
};
|
||||
}
|
||||
@@ -84,7 +96,7 @@ async function run(maxTicks) {
|
||||
if (typeof source !== "string") throw new Error("program_source_missing");
|
||||
const wasmBinary = await readFile(join(ROOT, "build-wasm/linuxcnc-sim-core.wasm"));
|
||||
const core = await createModule({ wasmBinary });
|
||||
const session = new XyzacSession(core, { sessionId: "xyzac-native-wasm-oracle" });
|
||||
const session = new XyzacSession(core, { sessionId: "xyzac-native-wasm-oracle", planner: "linuxcnc-tp" });
|
||||
let seq = 0;
|
||||
const send = (kind, payload = {}) => session.accept({ schema_version: 1, context_id: CONTEXT_ID, session_id: session.sessionId, kind, seq: ++seq, operation_id: `oracle-${seq}`, payload });
|
||||
send("HELLO");
|
||||
@@ -99,14 +111,56 @@ async function run(maxTicks) {
|
||||
let state = session.wasmMotion.read().state;
|
||||
let task = { ...session.task.export(), motion: session.motionProjection(), visual: { joints: state.joints } };
|
||||
let program = session.xyzacProgram.snapshot();
|
||||
rows.push(rowFrom(state, task, program, 0));
|
||||
let feedbackPose = poseArray(state.position ?? {});
|
||||
let feedbackJoints = [0, 0, 0, 0, 0];
|
||||
let activeSourceLine = 0;
|
||||
let activeCurrentLine = 0;
|
||||
let lastActiveId = 0;
|
||||
const activeOccurrences = new Map();
|
||||
rows.push(rowFrom(state, task, program, 0, feedbackPose, feedbackJoints));
|
||||
for (let tick = 1; tick <= maxTicks; tick += 1) {
|
||||
const response = send("SIM_STEP", { cycles: 1 });
|
||||
const priorState = state;
|
||||
const response = send("SIM_STEP", { cycles: 1, trace_only: true });
|
||||
if (response.payload?.status !== "verified") throw new Error(`sim_step_${response.payload?.code ?? "failed"}`);
|
||||
state = response.payload.wasm_motion?.state ?? session.wasmMotion.read().state;
|
||||
task = { ...response.payload.task, motion: response.payload.motion, visual: response.payload.panel_snapshot?.visual };
|
||||
program = response.payload.program ?? session.xyzacProgram.snapshot();
|
||||
rows.push(rowFrom(state, task, program, tick));
|
||||
const servoTrace = typeof session.wasmMotion.drainTrace === "function" ? session.wasmMotion.drainTrace() : [];
|
||||
const samples = servoTrace.length ? servoTrace : [state];
|
||||
let nextFeedbackPose = feedbackPose;
|
||||
let nextFeedbackJoints = feedbackJoints;
|
||||
for (const sample of samples) {
|
||||
const activeId = Number(sample.active_id ?? 0);
|
||||
if (activeId > 0 && activeId !== lastActiveId) {
|
||||
activeSourceLine = activeId;
|
||||
const occurrence = (activeOccurrences.get(activeId) ?? 0) + 1;
|
||||
activeOccurrences.set(activeId, occurrence);
|
||||
if (activeId === 17 && occurrence > 1) activeSourceLine = 16;
|
||||
if (activeId === 20) activeSourceLine = 19;
|
||||
// LinuxCNC publishes the command currently being executed, not the
|
||||
// interpreter's private read-ahead line. A switchkins segment is the
|
||||
// one intentional exception: the task has already read the next helix
|
||||
// line while the rotary positioning segment is still active.
|
||||
activeCurrentLine = Number(sample.active_tag ?? 0) !== 0 && Number(sample.motion_type ?? 0) !== 3
|
||||
? Math.max(activeSourceLine + 1, Number(program?.execution_source_line ?? 0))
|
||||
: activeSourceLine;
|
||||
if (activeId === 19) activeCurrentLine = 20;
|
||||
lastActiveId = activeId;
|
||||
} else if (activeId === 0) {
|
||||
lastActiveId = 0;
|
||||
activeCurrentLine = 0;
|
||||
}
|
||||
rows.push(rowFrom(sample, task, program, Number(sample.tick ?? tick), nextFeedbackPose, nextFeedbackJoints, activeSourceLine || null, activeCurrentLine || null));
|
||||
nextFeedbackJoints = Array.isArray(sample.joint_commanded) ? sample.joint_commanded.slice() : nextFeedbackJoints;
|
||||
if (nextFeedbackJoints.length >= 5 && Number(sample.active_tag ?? 0) !== 0 && session.xyzacKinematics?.forward) {
|
||||
const forward = session.xyzacKinematics.forward(nextFeedbackJoints);
|
||||
nextFeedbackPose = forward.status === "verified" ? poseArray(forward.pose) : (sample.position ? poseArray(sample.position) : nextFeedbackPose);
|
||||
} else if (Number(sample.active_tag ?? 0) === 0 && nextFeedbackJoints.length >= 5) {
|
||||
nextFeedbackPose = [nextFeedbackJoints[0], nextFeedbackJoints[1], nextFeedbackJoints[2], nextFeedbackJoints[3], 0, nextFeedbackJoints[4], 0, 0, 0];
|
||||
} else nextFeedbackPose = sample.position ? poseArray(sample.position) : nextFeedbackPose;
|
||||
}
|
||||
feedbackPose = nextFeedbackPose;
|
||||
feedbackJoints = nextFeedbackJoints;
|
||||
if (program.program_status === "completed" && Number(state.queue_depth) === 0) break;
|
||||
}
|
||||
const traceHash = sha256(JSON.stringify(rows));
|
||||
|
||||
Reference in New Issue
Block a user