继续完成 web-rtcp-5axis-sim-plan

结论:完成 LinuxCNC kinematics WASM ABI 覆盖,并将 web-rtcp-5axis-sim-plan 的 RTCP frame/boundary adapter 接到 xyzac-trt kinematics SDK;Node、build、browser smoke 验证通过。
This commit is contained in:
2026-06-21 16:44:29 +08:00
parent a6eda3fbff
commit 626bcfe8e3
101 changed files with 101586 additions and 770 deletions

View File

@@ -0,0 +1,50 @@
import assert from "node:assert/strict";
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
import { buildRtcpFrame } from "../../app/src/runtime/rtcp-frame.js";
const runtime = await createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" });
const readiness = runtime.readiness();
assert.equal(runtime.apiName, "web-rtcp-5axis-linuxcnc-kinematics-runtime");
assert.equal(runtime.moduleId, "xyzac-trt");
assert.equal(runtime.loaded, true);
assert.equal(runtime.sourceMode, "source-derived-kinematics-wasm");
assert.equal(runtime.semanticBoundary, "linuxcnc_kinematics_wasm_c_abi");
assert.equal(runtime.wasmFile, "linuxcnc_xyzac_trt_kinematics.wasm");
assert.equal(readiness.loaded, true);
assert.equal(readiness.supportedModules.includes("xyzac-trt"), true);
assert.equal(runtime.switchRc, 0);
const linuxCncKinematicsResult = runtime.frameForJoints([10, 20, 30, 25, 40]);
assert.equal(linuxCncKinematicsResult.moduleId, "xyzac-trt");
assert.equal(linuxCncKinematicsResult.forward.rc, 0);
assert.equal(linuxCncKinematicsResult.inverse.rc, 0);
assert.deepEqual(
linuxCncKinematicsResult.inverse.joints.map((value) => Math.round(value * 1e6) / 1e6),
[10, 20, 30, 25, 40],
);
const frame = buildRtcpFrame({
axisPose: { x: 10, y: 20, z: 30, a: 25, b: 0, c: 40 },
activeLine: 777,
kinsType: "tcp-xyzac",
rtcpEnabled: true,
linuxCncKinematicsResult,
});
assert.equal(frame.sourceMode, "source-derived-kinematics-wasm");
assert.equal(frame.semanticBoundary, "linuxcnc_kinematics_wasm_c_abi");
assert.equal(frame.readiness.linuxCncKinematicsReady, true);
assert.equal(frame.readiness.promotionAllowed, true);
assert.equal(frame.readiness.fullLinuxCncProgramExecutionReady, false);
assert.equal(frame.kinematicsModuleId, "xyzac-trt");
assert.equal(frame.kinematicsForwardRc, 0);
assert.equal(frame.kinematicsInverseRc, 0);
assert.equal(frame.jointPose[3].value, 25);
assert.equal(frame.jointPose[4].value, 40);
assert.equal(frame.tcpPose.x, linuxCncKinematicsResult.forward.pose.x);
assert.equal(frame.tcpPose.y, linuxCncKinematicsResult.forward.pose.y);
assert.equal(frame.tcpPose.z, linuxCncKinematicsResult.forward.pose.z);
console.log("linuxcnc_kinematics_runtime_smoke=ok");

View File

@@ -0,0 +1,93 @@
import assert from "node:assert/strict";
import { xyzacTrtProfile } from "../../app/src/profiles/xyzac-trt.js";
import { createPyvcpHalBindingSummary, xyzacTrtPyvcpPanelSchema } from "../../app/src/panel-schema/xyzac-trt-pyvcp.js";
import { createLinuxCncBoundaryAdapter, createLinuxCncBoundaryReadiness } from "../../app/src/runtime/linuxcnc-boundary-adapter.js";
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
import { createProfileSourceReferenceSummary } from "../../app/src/profiles/source-reference-map.js";
assert.equal(xyzacTrtProfile.id, "xyzac-trt");
assert.equal(xyzacTrtProfile.iniPath.endsWith("xyzac-trt.ini"), true);
assert.deepEqual(xyzacTrtProfile.coordinates, ["X", "Y", "Z", "A", "C"]);
assert.equal(xyzacTrtProfile.kinematics, "xyzac-trt-kins");
assert.equal(xyzacTrtProfile.machineName, "sim-xyzac-trt-kins (switchkins)");
assert.equal(xyzacTrtProfile.display.jogAxes.join(""), "XYZC");
assert.equal(xyzacTrtProfile.rs274ngc.halPinVars, true);
assert.equal(xyzacTrtProfile.traj.coordinates, "XYZAC");
assert.equal(xyzacTrtProfile.axisLimits.A.max, 50);
assert.equal(xyzacTrtProfile.jointConfig.length, 5);
assert.equal(xyzacTrtProfile.halui.mdiCommands.join(","), "M429,M428,M430");
assert.equal(xyzacTrtProfile.hal.halcmd.switchkinsSelectNet.target, "motion.switchkins-type");
assert.equal(xyzacTrtProfile.hal.halcmd.feedbackNets.length, 5);
assert.equal(xyzacTrtProfile.hal.halcmd.offsetNets.length, 4);
assert.equal(xyzacTrtProfile.toolTable.toolCount, 10);
assert.equal(xyzacTrtProfile.toolTable.tools[1].zOffset, 15);
assert.equal(xyzacTrtProfile.kinematicsParameters.sparm, "identityfirst");
assert.equal(xyzacTrtProfile.remaps.map((remap) => remap.code).join(","), "M428,M429,M430");
assert.equal(xyzacTrtProfile.remaps.every((remap) => remap.analogOutputIndex === 3), true);
assert.equal(xyzacTrtProfile.halPins.includes("motion.switchkins-type"), true);
assert.equal(xyzacTrtProfile.halPins.includes("xyzac-trt-kins.tool-offset"), true);
assert.equal(xyzacTrtProfile.promotionAllowed, false);
assert.equal(xyzacTrtProfile.linuxCncKinematicsReady, false);
const sourceSummary = createProfileSourceReferenceSummary("xyzac-trt");
assert.equal(sourceSummary.referenceCount >= 8, true);
assert.equal(sourceSummary.kinds.includes("tool_table"), true);
assert.equal(sourceSummary.sourceRequiredCount >= 3, true);
assert.equal(sourceSummary.promotionAllowed, false);
assert.equal(sourceSummary.semanticBoundary, "profile_source_map_only_not_runtime_proof");
assert.ok(sourceSummary.references.some((reference) => reference.path.endsWith("xyzac-trt.ini")));
assert.ok(sourceSummary.references.some((reference) => reference.path.endsWith("trtfuncs.c")));
const panelSummary = createPyvcpHalBindingSummary(xyzacTrtPyvcpPanelSchema);
assert.equal(panelSummary.schemaId, "xyzac-trt-switchkins-pyvcp");
assert.equal(panelSummary.controlCount, 5);
assert.equal(panelSummary.buttonCount, 4);
assert.deepEqual(panelSummary.mdiCommands, ["M429", "M428", "M430"]);
assert.ok(panelSummary.halNets.some((net) => net.target === "halui.mdi-command-01"));
assert.equal(panelSummary.promotionAllowed, false);
const adapter = createLinuxCncBoundaryAdapter();
assert.equal(adapter.apiName, "web-rtcp-5axis-linuxcnc-boundary-adapter");
assert.equal(adapter.profileId, "xyzac-trt");
assert.equal(adapter.runtimeReady, false);
assert.equal(adapter.kinematicsRuntimeReady, false);
assert.equal(adapter.interpreterRuntimeReady, false);
assert.equal(adapter.linuxCncKinematicsReady, false);
assert.equal(adapter.promotionAllowed, false);
assert.equal(adapter.profileSummary.coordinates, "XYZAC");
assert.equal(adapter.profileSummary.jointCount, 5);
assert.equal(adapter.profileSummary.mdiCommandCount, 3);
assert.equal(adapter.profileSummary.toolCount, 10);
assert.equal(adapter.profileSummary.feedbackNetCount, 5);
assert.equal(adapter.adapterPoints.pyvcpSchemaId, "xyzac-trt-switchkins-pyvcp");
assert.equal(adapter.adapterPoints.halPins.includes("halui.mdi-command-00"), true);
const readiness = createLinuxCncBoundaryReadiness(adapter);
assert.equal(readiness.ready, false);
assert.equal(readiness.linuxCncKinematicsReady, false);
assert.equal(readiness.promotionAllowed, false);
assert.ok(readiness.missing.includes("kinematics runtime"));
assert.ok(readiness.missing.includes("interpreter/remap runtime"));
const runtime = await createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" });
const kinematicsAdapter = createLinuxCncBoundaryAdapter({
runtime: {
kinematicsWasm: runtime.readiness(),
interpreterWasm: null,
},
});
const kinematicsReadiness = createLinuxCncBoundaryReadiness(kinematicsAdapter);
assert.equal(kinematicsAdapter.runtimeReady, false);
assert.equal(kinematicsAdapter.kinematicsRuntimeReady, true);
assert.equal(kinematicsAdapter.interpreterRuntimeReady, false);
assert.equal(kinematicsAdapter.linuxCncKinematicsReady, true);
assert.equal(kinematicsAdapter.promotionAllowed, true);
assert.equal(kinematicsAdapter.fullLinuxCncProgramExecutionReady, false);
assert.equal(kinematicsAdapter.semanticBoundary, "linuxcnc_kinematics_wasm_runtime_connected");
assert.equal(kinematicsReadiness.ready, true);
assert.equal(kinematicsReadiness.linuxCncKinematicsReady, true);
assert.equal(kinematicsReadiness.fullLinuxCncProgramExecutionReady, false);
assert.ok(kinematicsReadiness.missing.includes("interpreter/remap runtime"));
console.log("profile_boundary_smoke=ok");

View File

@@ -0,0 +1,214 @@
import assert from "node:assert/strict";
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
import { buildRtcpFrame } from "../../app/src/runtime/rtcp-frame.js";
import { createSimulationStore } from "../../app/src/state/store.js";
const identityFrame = buildRtcpFrame({
axisPose: { x: 43, y: -32.15, z: -11.306, a: 0, b: 0, c: 0 },
activeLine: 501,
kinsType: "identity",
rtcpEnabled: false,
});
assert.equal(identityFrame.apiName, "web-rtcp-5axis-motion-frame");
assert.equal(identityFrame.rtcpState, "off");
assert.equal(identityFrame.readiness.frameReady, true);
assert.equal(identityFrame.readiness.linuxCncKinematicsReady, false);
assert.equal(identityFrame.readiness.promotionAllowed, false);
assert.equal(identityFrame.semanticBoundary, "fixture_frame_ui_plumbing_not_linuxcnc_kinematics_proof");
assert.deepEqual(identityFrame.compensation, { x: 0, y: 0, z: 0 });
const tcpFrame = buildRtcpFrame({
axisPose: { x: 43, y: -32.15, z: -11.306, a: 15, b: 0, c: 30 },
activeLine: 502,
kinsType: "tcp-xyzac",
rtcpEnabled: true,
});
assert.equal(tcpFrame.rtcpState, "on");
assert.equal(tcpFrame.kinsType, "tcp-xyzac");
assert.notEqual(tcpFrame.compensation.z, 0);
assert.ok(Number.isFinite(tcpFrame.tcpPose.x));
assert.ok(Number.isFinite(tcpFrame.toolAxisVector.z));
assert.equal(tcpFrame.jointPose.length, 5);
const runtime = await createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" });
const linuxCncKinematicsResult = runtime.frameForJoints([10, 20, 30, 25, 40]);
const linuxCncFrame = buildRtcpFrame({
axisPose: { x: 10, y: 20, z: 30, a: 25, b: 0, c: 40 },
activeLine: 503,
kinsType: "tcp-xyzac",
rtcpEnabled: true,
linuxCncKinematicsResult,
});
assert.equal(linuxCncFrame.sourceMode, "source-derived-kinematics-wasm");
assert.equal(linuxCncFrame.semanticBoundary, "linuxcnc_kinematics_wasm_c_abi");
assert.equal(linuxCncFrame.readiness.linuxCncKinematicsReady, true);
assert.equal(linuxCncFrame.readiness.promotionAllowed, true);
assert.equal(linuxCncFrame.readiness.fullLinuxCncProgramExecutionReady, false);
assert.equal(linuxCncFrame.kinematicsModuleId, "xyzac-trt");
assert.equal(linuxCncFrame.jointPose[3].value, 25);
assert.equal(linuxCncFrame.jointPose[4].value, 40);
const fixtureInitialStore = createSimulationStore();
assert.equal(fixtureInitialStore.getState().rtcpState, "off");
assert.equal(fixtureInitialStore.getState().machine.powerOn, false);
assert.equal(fixtureInitialStore.getState().machine.mode, "manual");
assert.equal(
fixtureInitialStore.getState().linuxCncBoundaryAdapter.apiName,
"web-rtcp-5axis-linuxcnc-boundary-adapter",
);
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryAdapter.panelSummary.schemaId, "xyzac-trt-switchkins-pyvcp");
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryAdapter.sourceSummary.referenceCount >= 8, true);
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryAdapter.profileSummary.toolCount, 10);
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryAdapter.profileSummary.coordinates, "XYZAC");
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryReadiness.ready, false);
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryReadiness.promotionAllowed, false);
const kinematicsStore = createSimulationStore();
kinematicsStore.dispatch({ type: "ATTACH_KINEMATICS_RUNTIME", runtime });
let kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.linuxCncBoundaryAdapter.linuxCncKinematicsReady, true);
assert.equal(kinematicsState.linuxCncBoundaryAdapter.interpreterRuntimeReady, false);
assert.equal(kinematicsState.linuxCncBoundaryReadiness.ready, true);
assert.equal(kinematicsState.linuxCncBoundaryReadiness.fullLinuxCncProgramExecutionReady, false);
assert.equal(kinematicsState.linuxCncBoundaryReadiness.missing.includes("interpreter/remap runtime"), true);
assert.equal(kinematicsState.rtcpFrame.sourceMode, "source-derived-kinematics-wasm");
assert.equal(kinematicsState.rtcpFrame.semanticBoundary, "linuxcnc_kinematics_wasm_c_abi");
assert.equal(kinematicsState.rtcpFrame.readiness.linuxCncKinematicsReady, true);
kinematicsStore.dispatch({ type: "SET_RTCP", enabled: true });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.rtcpState, "on");
assert.equal(kinematicsState.rtcpFrame.sourceMode, "source-derived-kinematics-wasm");
assert.equal(kinematicsState.lastKinematicsResult.moduleId, "xyzac-trt");
assert.equal(kinematicsState.dro.tcpX, kinematicsState.rtcpFrame.tcpPose.x);
kinematicsStore.dispatch({ type: "TOGGLE_POWER" });
kinematicsStore.dispatch({ type: "STEP" });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.runState, "stepping");
assert.equal(kinematicsState.rtcpFrame.sourceMode, "source-derived-kinematics-wasm");
assert.equal(kinematicsState.rtcpFrame.readiness.fullLinuxCncProgramExecutionReady, false);
kinematicsStore.dispatch({ type: "SET_FRAME_SOURCE", sourceMode: "fixture-ui-only" });
kinematicsState = kinematicsStore.getState();
assert.equal(kinematicsState.rtcpFrame.sourceMode, "fixture-ui-only");
assert.equal(kinematicsState.rtcpFrame.readiness.linuxCncKinematicsReady, false);
const store = createSimulationStore();
store.dispatch({ type: "RUN" });
assert.equal(store.getState().activeLine, 501);
assert.equal(store.getState().operatorMessage, "run blocked: power or estop state");
store.dispatch({ type: "TOGGLE_POWER" });
assert.equal(store.getState().machine.powerOn, true);
store.dispatch({ type: "SET_RTCP", enabled: true });
let state = store.getState();
assert.equal(state.rtcpState, "on");
assert.equal(state.kinsType, "tcp-xyzac");
assert.equal(state.rtcpFrame.readiness.linuxCncKinematicsReady, false);
assert.notEqual(state.dro.tcpZ, state.dro.z);
const previousLine = state.activeLine;
const previousTcpX = state.tcpPose.x;
store.dispatch({ type: "STEP" });
state = store.getState();
assert.equal(state.activeLine, previousLine + 1);
assert.equal(state.runState, "stepping");
assert.notEqual(state.tcpPose.x, previousTcpX);
store.dispatch({ type: "JOG", axis: "x", direction: 1, increment: 2 });
state = store.getState();
assert.equal(state.machine.mode, "jog");
assert.equal(state.runState, "jogging");
assert.equal(Math.round(state.axisPose.x), Math.round(state.rtcpFrame.axisPose.x));
store.dispatch({ type: "RUN_MDI", command: "G0 X1" });
state = store.getState();
assert.equal(state.machine.mode, "mdi");
assert.equal(state.machine.mdiCommand, "G0 X1");
assert.equal(state.runState, "mdi");
store.dispatch({
type: "LOAD_PROGRAM",
filename: "operator-demo.ngc",
content: "G0 X0 Y0\nG1 X10 F100\nM30\n",
});
state = store.getState();
assert.equal(state.activeProgram, "operator-demo.ngc");
assert.equal(state.programSource, "operator-file");
assert.equal(state.programStartLine, 1);
assert.equal(state.activeLine, 1);
assert.equal(state.programLines.length, 3);
assert.equal(state.machine.mode, "auto");
store.dispatch({ type: "RUN" });
state = store.getState();
assert.equal(state.activeLine, 3);
assert.equal(state.runState, "complete");
store.dispatch({ type: "SET_RTCP", enabled: false });
state = store.getState();
assert.equal(state.rtcpState, "off");
assert.equal(state.kinsType, "identity");
assert.equal(state.dro.tcpX, state.dro.x);
store.dispatch({ type: "ADJUST_OVERRIDE", target: "feed", delta: -10 });
state = store.getState();
assert.equal(state.feed.feedOverride, 90);
assert.equal(state.operatorMessage, "feed override adjusted");
store.dispatch({ type: "ADJUST_OVERRIDE", target: "rapid", delta: 20 });
state = store.getState();
assert.equal(state.feed.rapidOverride, 120);
store.dispatch({ type: "ADJUST_SPINDLE_OVERRIDE", delta: 10 });
state = store.getState();
assert.equal(state.spindle.override, 110);
store.dispatch({ type: "TOGGLE_COOLANT", kind: "flood" });
state = store.getState();
assert.equal(state.coolant.flood, false);
store.dispatch({ type: "TOGGLE_COOLANT", kind: "mist" });
state = store.getState();
assert.equal(state.coolant.mist, true);
store.dispatch({ type: "SET_VIEW", view: "x" });
state = store.getState();
assert.equal(state.preview.selectedView, "x");
store.dispatch({ type: "CLEAR_PREVIEW" });
state = store.getState();
assert.equal(state.preview.pathPoints, 0);
store.dispatch({ type: "RELOAD_PROGRAM" });
state = store.getState();
assert.equal(state.preview.pathPoints, 3);
assert.equal(state.activeLine, 1);
assert.equal(state.runState, "idle");
store.dispatch({ type: "TOGGLE_FULLSCREEN" });
state = store.getState();
assert.equal(state.preview.fullscreen, true);
store.dispatch({ type: "HOME" });
state = store.getState();
assert.equal(state.axisPose.x, 43);
assert.equal(state.operatorMessage, "machine homed to fixture origin");
store.dispatch({ type: "ESTOP" });
state = store.getState();
assert.equal(state.machine.powerOn, false);
assert.equal(state.machine.estopActive, true);
assert.equal(state.runState, "estopped");
store.dispatch({ type: "RESET" });
state = store.getState();
assert.equal(state.machine.estopActive, false);
assert.equal(state.operatorMessage, "machine reset complete");
console.log("rtcp_store_smoke=ok");