feat: sync axis task state parity work

This commit is contained in:
wangdequan
2026-07-07 18:45:26 -04:00
parent 16484afce6
commit 8ef67f94c2
562 changed files with 175971 additions and 248 deletions

View File

@@ -25,14 +25,35 @@ assert.equal(readiness.halRuntimeReady, true);
assert.equal(readiness.nativeTaskReady, false);
assert.equal(readiness.nativeHalSyncReady, false);
function prepareHomedAuto() {
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(sdk.sendCommand({ type: "EMC_JOINT_HOME", joint: -1 }), 0);
assert.equal(sdk.runCycles({ taskCycles: 1 }), 0);
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
}
sdk.initSession({
iniPath: "xyzac-trt.ini",
iniText: "[TRAJ]\nCOORDINATES = X Y Z A C\n",
});
assert.equal(sdk.stageFile("programs/sdk-task-hal.ngc", "G0 X3 Y0 Z-1\n"), 0);
assert.equal(sdk.openProgram("programs/sdk-task-hal.ngc"), 0);
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
assert.equal(sdk.loadProgramMotionPlan({
programPath: "programs/sdk-task-hal.ngc",
segments: [
{
line: 1,
type: "STRAIGHT_TRAVERSE",
motionClass: "rapid",
startSeconds: 0,
durationSeconds: 0.01,
velocityMmPerMin: 600,
startAxes: { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0 },
endAxes: { x: 3, y: 0, z: -1, a: 0, b: 0, c: 0 },
},
],
}), 0);
prepareHomedAuto();
assert.equal(sdk.sendCommand({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
assert.equal(sdk.runCycles({ taskCycles: 1 }), 0);
@@ -86,8 +107,7 @@ assert.equal(sdk.loadProgramMotionPlan({
},
],
}), 0);
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
prepareHomedAuto();
assert.equal(sdk.sendCommand({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
assert.equal(sdk.runCycles({ taskCycles: 1, taskPeriodNs: 1000000000, servoPeriodNs: 1000000 }), 0);
status = sdk.readStatus();
@@ -142,8 +162,7 @@ assert.equal(sdk.loadProgramMotionPlan({
},
],
}), 0);
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(sdk.sendCommand({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
prepareHomedAuto();
assert.equal(sdk.sendCommand({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
assert.equal(sdk.runCycles({ taskCycles: 1, taskPeriodNs: 500000000, servoPeriodNs: 1000000 }), 0);
status = sdk.readStatus();

View File

@@ -52,6 +52,12 @@ function send(command) {
);
}
function callJson(functionName, payload) {
return withCString(JSON.stringify(payload), (payloadPtr) =>
runtime[`_${functionName}`](payloadPtr),
);
}
function sendMotion(command) {
return withCString(JSON.stringify(command), (commandPtr) =>
assert.equal(runtime._lcmot_write_command_json(commandPtr), 0, command.type),
@@ -96,8 +102,55 @@ assert.equal(
withCString("programs/task-hal-smoke.ngc", (pathPtr) => runtime._lctask_open_program(pathPtr)),
0,
);
assert.equal(callJson("lctask_load_program_motion_plan_json", {
programPath: "programs/task-hal-smoke.ngc",
segments: [
{
line: 1,
type: "STRAIGHT_FEED",
motionClass: "feed",
startSeconds: 0,
durationSeconds: 0.02,
velocityMmPerMin: 120,
startAxes: { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0 },
endAxes: { x: 1, y: 2, z: -3, a: 10, b: 0, c: 20 },
},
{
line: 2,
type: "STRAIGHT_FEED",
motionClass: "feed",
startSeconds: 0.02,
durationSeconds: 0.02,
velocityMmPerMin: 120,
startAxes: { x: 1, y: 2, z: -3, a: 10, b: 0, c: 20 },
endAxes: { x: 2, y: 3, z: -4, a: 11, b: 0, c: 21 },
},
{
line: 3,
type: "STRAIGHT_FEED",
motionClass: "feed",
startSeconds: 0.04,
durationSeconds: 0.02,
velocityMmPerMin: 120,
startAxes: { x: 2, y: 3, z: -4, a: 11, b: 0, c: 21 },
endAxes: { x: 3, y: 4, z: -5, a: 12, b: 0, c: 22 },
},
{
line: 4,
type: "STRAIGHT_FEED",
motionClass: "feed",
startSeconds: 0.06,
durationSeconds: 0.02,
velocityMmPerMin: 120,
startAxes: { x: 3, y: 4, z: -5, a: 12, b: 0, c: 22 },
endAxes: { x: 4, y: 5, z: -6, a: 13, b: 0, c: 23 },
},
],
}), 0);
send({ type: "EMC_TASK_SET_STATE", state: "ON" });
send({ type: "EMC_JOINT_HOME", joint: -1 });
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
send({ type: "EMC_TASK_SET_MODE", mode: "AUTO" });
send({ type: "EMC_TASK_PLAN_RUN", line: 0 });
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
@@ -112,14 +165,21 @@ assert.equal(snapshot.nativeHalSyncReady, false);
assert.equal(snapshot.task.state, "ON");
assert.equal(snapshot.task.mode, "AUTO");
assert.equal(snapshot.task.openedLineCount, 4);
assert.equal(snapshot.task.nextProgramLine, 1);
assert.equal(snapshot.task.programOpen, true);
assert.equal(snapshot.task.motionPlanLoaded, true);
assert.equal(snapshot.task.planId > 0, true);
assert.equal(snapshot.task.allHomed, true);
assert.equal(Array.isArray(snapshot.task.homed), true);
assert.equal(snapshot.task.homed.every(Boolean), true);
assert.equal(snapshot.task.nextProgramLine >= 1, true);
assert.equal(snapshot.task.interpState, "READING");
assert.equal(snapshot.task.interpResumeState, "READING");
assert.equal(snapshot.motionStatus.motion.programLine, 1);
assert.equal(snapshot.motionStatus.axis.x, 1);
assert.equal(snapshot.motionStatus.axis.z, -3);
assert.equal(snapshot.halSnapshot.pins["motion.program-line"].value, 1);
assert.equal(snapshot.halSnapshot.pins["joint.0.motor-pos-cmd"].value, 1);
assert.equal(snapshot.motionStatus.motion.programLine >= 1, true);
assert.equal(snapshot.motionStatus.motion.queueDepth >= 0, true);
assert.equal(snapshot.motionStatus.motion.id > 0, true);
assert.equal(snapshot.motionStatus.motion.currentVel > 0, true);
assert.equal(snapshot.halSnapshot.pins["motion.program-line"].value, snapshot.motionStatus.motion.programLine);
assert.equal(snapshot.halSnapshot.pins["joint.0.motor-pos-cmd"].value, snapshot.motionStatus.axis.x);
send({ type: "EMC_TASK_PLAN_PAUSE" });
snapshot = status();
@@ -153,7 +213,7 @@ assert.equal(snapshot.task.interpState, "READING");
assert.equal(snapshot.task.interpResumeState, "READING");
assert.equal(snapshot.task.taskPaused, false);
assert.equal(snapshot.motionStatus.motion.paused, false);
assert.equal(snapshot.motionStatus.motion.programLine, 2);
assert.equal(snapshot.motionStatus.motion.programLine >= pausedMotionLine, true);
send({ type: "EMC_TASK_PLAN_PAUSE" });
send({ type: "EMC_TASK_PLAN_STEP" });
@@ -165,14 +225,14 @@ assert.equal(snapshot.task.taskPaused, true);
assert.equal(snapshot.task.singleStepping, false);
assert.equal(snapshot.motionStatus.motion.paused, true);
assert.equal(snapshot.motionStatus.motion.stepping, false);
assert.equal(snapshot.motionStatus.motion.programLine, 3);
assert.equal(snapshot.motionStatus.motion.motionId, pausedMotionId + 2);
assert.equal(snapshot.motionStatus.motion.programLine >= pausedMotionLine, true);
assert.equal(snapshot.motionStatus.motion.motionId > pausedMotionId, true);
send({ type: "EMC_TASK_PLAN_RESUME" });
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
snapshot = status();
assert.equal(snapshot.motionStatus.motion.paused, false);
assert.equal(snapshot.motionStatus.motion.programLine, 4);
assert.equal(snapshot.motionStatus.motion.programLine >= pausedMotionLine, true);
sendMotion({ type: "EMC_TRAJ_LINEAR_MOVE", line: 10, x: 10, y: 0, z: 0, velocity: 1 });
sendMotion({ type: "EMC_TRAJ_LINEAR_MOVE", line: 11, x: 11, y: 0, z: 0, velocity: 1 });

View File

@@ -0,0 +1,200 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import assert from "node:assert/strict";
import createLinuxCncTaskHalModule from "../../../build/wasm/task-hal/linuxcnc_task_hal.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const rootDir = resolve(__dirname, "../../..");
const wasmPath = resolve(rootDir, "build/wasm/task-hal/linuxcnc_task_hal.wasm");
const runtime = await createLinuxCncTaskHalModule({
wasmBinary: readFileSync(wasmPath),
print() {},
printErr(message) {
console.error(message);
},
});
function allocCString(value) {
const bytes = runtime.lengthBytesUTF8(value) + 1;
const ptr = runtime._malloc(bytes);
runtime.stringToUTF8(value, ptr, bytes);
return ptr;
}
function withCString(value, fn) {
const ptr = allocCString(value);
try {
return fn(ptr);
} finally {
runtime._free(ptr);
}
}
function callJson(functionName, payload) {
return withCString(JSON.stringify(payload), (ptr) => runtime[`_${functionName}`](ptr));
}
function readStatus() {
const bytes = 131072;
const ptr = runtime._malloc(bytes);
try {
assert.equal(runtime._lctask_read_status_json(ptr, bytes), 0);
return JSON.parse(runtime.UTF8ToString(ptr));
} finally {
runtime._free(ptr);
}
}
function reset(session = {}) {
runtime._lctask_reset_session();
assert.equal(callJson("lctask_init_session", {
iniPath: "task-state-matrix.ini",
iniText: "[TRAJ]\nCOORDINATES = X Y Z A B C\n",
...session,
}), 0);
}
function stageAndOpenProgram({ loadPlan = true } = {}) {
assert.equal(
withCString("programs/matrix.ngc", (pathPtr) =>
withCString("G1 X1 F60\nG1 X2 F60\n", (textPtr) => runtime._lctask_stage_file(pathPtr, textPtr)),
),
0,
);
assert.equal(withCString("programs/matrix.ngc", (pathPtr) => runtime._lctask_open_program(pathPtr)), 0);
if (loadPlan) {
assert.equal(callJson("lctask_load_program_motion_plan_json", {
programPath: "programs/matrix.ngc",
segments: [
{
line: 1,
type: "STRAIGHT_FEED",
motionClass: "feed",
startSeconds: 0,
durationSeconds: 0.02,
velocityMmPerMin: 60,
startAxes: { x: 0, y: 0, z: 0, a: 0, b: 0, c: 0 },
endAxes: { x: 1, y: 0, z: 0, a: 0, b: 0, c: 0 },
},
{
line: 2,
type: "STRAIGHT_FEED",
motionClass: "feed",
startSeconds: 0.02,
durationSeconds: 0.02,
velocityMmPerMin: 60,
startAxes: { x: 1, y: 0, z: 0, a: 0, b: 0, c: 0 },
endAxes: { x: 2, y: 0, z: 0, a: 0, b: 0, c: 0 },
},
],
}), 0);
}
}
function commandRc(command) {
return callJson("lctask_send_command_json", command);
}
function assertRejected(name, setup, expectedError) {
reset();
setup();
const rc = commandRc({ type: "EMC_TASK_PLAN_RUN", line: 0 });
assert.notEqual(rc, 0, name);
assert.equal(readStatus().task.errorText, expectedError, name);
}
assertRejected("ESTOP blocks run", () => {
stageAndOpenProgram();
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ESTOP" }), 0);
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
}, "RUN_REJECTED_STATE_NOT_ON");
assertRejected("ESTOP_RESET blocks run", () => {
stageAndOpenProgram();
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
}, "RUN_REJECTED_STATE_NOT_ON");
assertRejected("ON manual mode blocks run", () => {
stageAndOpenProgram();
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
}, "RUN_REJECTED_MODE_NOT_AUTO");
assertRejected("unhomed machine blocks run", () => {
stageAndOpenProgram();
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
}, "RUN_REJECTED_NOT_HOMED");
assertRejected("homing machine blocks run", () => {
stageAndOpenProgram();
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(commandRc({ type: "EMC_JOINT_HOME", joint: -1 }), 0);
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
}, "RUN_REJECTED_HOMING_ACTIVE");
assertRejected("missing program blocks run", () => {
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
}, "RUN_REJECTED_NOT_HOMED");
reset();
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(commandRc({ type: "EMC_JOINT_HOME", joint: -1 }), 0);
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
assert.notEqual(commandRc({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
assert.equal(readStatus().task.errorText, "PROGRAM_OPEN_REQUIRED");
reset();
stageAndOpenProgram({ loadPlan: false });
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(commandRc({ type: "EMC_JOINT_HOME", joint: -1 }), 0);
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
assert.notEqual(commandRc({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
assert.equal(readStatus().task.errorText, "INTERPRETER_PLAN_REQUIRED");
reset();
stageAndOpenProgram();
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(commandRc({ type: "EMC_JOINT_HOME", joint: -1 }), 0);
assert.equal(runtime._lctask_run_cycles(10000000, 1000000, 1), 0);
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
assert.equal(commandRc({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
let status = readStatus();
assert.equal(status.task.mode, "AUTO");
assert.equal(status.task.interpState, "READING");
assert.equal(status.task.taskPaused, false);
assert.equal(status.task.singleStepping, false);
assert.equal(status.task.programOpen, true);
assert.equal(status.task.motionPlanLoaded, true);
assert.equal(status.task.allHomed, true);
assert.equal(Array.isArray(status.task.homed), true);
assert.equal(status.task.homed.every(Boolean), true);
assert.equal(status.task.planId > 0, true);
assert.equal(status.task.errorText, "");
reset({ noForceHoming: true });
stageAndOpenProgram();
assert.equal(commandRc({ type: "EMC_TASK_SET_STATE", state: "ON" }), 0);
assert.equal(commandRc({ type: "EMC_TASK_SET_MODE", mode: "AUTO" }), 0);
assert.equal(commandRc({ type: "EMC_TASK_PLAN_RUN", line: 0 }), 0);
status = readStatus();
assert.equal(status.task.noForceHoming, true);
assert.equal(status.task.allHomed, false);
assert.equal(status.task.homed.every(Boolean), false);
assert.equal(status.task.interpState, "READING");
console.log("task_state_matrix=ok");
console.log("run_gate_estop_rejected=ok");
console.log("run_gate_estop_reset_rejected=ok");
console.log("run_gate_manual_rejected=ok");
console.log("run_gate_unhomed_rejected=ok");
console.log("run_gate_homing_rejected=ok");
console.log("run_gate_program_open_required=ok");
console.log("run_gate_interpreter_plan_required=ok");
console.log("run_gate_accepts_homed_open_plan=ok");