接入 LinuxCNC TP 运行反馈
This commit is contained in:
228
wasm-port/runtime/sdk/src/linuxcnc-tp.js
Normal file
228
wasm-port/runtime/sdk/src/linuxcnc-tp.js
Normal file
@@ -0,0 +1,228 @@
|
||||
import createLinuxCncTpModule from "../../../build/wasm/tp/linuxcnc_tp.js";
|
||||
|
||||
const SEMANTIC_BOUNDARY = "linuxcnc_tp_queue_runtime_timing_from_canonical_motion";
|
||||
|
||||
function allocCString(mod, value) {
|
||||
const bytes = mod.lengthBytesUTF8(value) + 1;
|
||||
const ptr = mod._malloc(bytes);
|
||||
mod.stringToUTF8(value, ptr, bytes);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function callTiming(mod, payload) {
|
||||
const inputPtr = allocCString(mod, JSON.stringify(payload));
|
||||
let resultPtr = 0;
|
||||
try {
|
||||
resultPtr = mod._lctp_run_canonical_motion_timing(inputPtr);
|
||||
if (!resultPtr) {
|
||||
throw new Error("lctp_run_canonical_motion_timing returned null");
|
||||
}
|
||||
const text = mod.UTF8ToString(resultPtr);
|
||||
const result = JSON.parse(text);
|
||||
if (result.ok !== true) {
|
||||
throw new Error(result.error || "LinuxCNC TP queue timing failed");
|
||||
}
|
||||
return result;
|
||||
} finally {
|
||||
if (resultPtr) mod._lctp_free_string(resultPtr);
|
||||
mod._free(inputPtr);
|
||||
}
|
||||
}
|
||||
|
||||
export async function createLinuxCncTpSdk(moduleOptions = {}) {
|
||||
const mod = await createLinuxCncTpModule(moduleOptions);
|
||||
|
||||
return {
|
||||
apiName: "linuxcnc-tp-wasm-sdk",
|
||||
semanticBoundary: SEMANTIC_BOUNDARY,
|
||||
module: mod,
|
||||
|
||||
readiness() {
|
||||
return {
|
||||
apiName: "linuxcnc-tp-wasm-sdk-readiness",
|
||||
loaded: true,
|
||||
semanticBoundary: SEMANTIC_BOUNDARY,
|
||||
runCanonicalMotionTimingReady: typeof mod._lctp_run_canonical_motion_timing === "function",
|
||||
};
|
||||
},
|
||||
|
||||
runCanonicalMotionTiming({ motion = [], options = {} } = {}) {
|
||||
const result = callTiming(mod, {
|
||||
...options,
|
||||
motion: motion.map((event, index) => ({
|
||||
index,
|
||||
line: Number.isFinite(Number(event.line)) ? Number(event.line) : -1,
|
||||
type: event.type,
|
||||
feedRate: Number.isFinite(Number(event.feedRate)) ? Number(event.feedRate) : 0,
|
||||
x: numberOrZero(event.axes?.x),
|
||||
y: numberOrZero(event.axes?.y),
|
||||
z: numberOrZero(event.axes?.z),
|
||||
a: numberOrZero(event.axes?.a),
|
||||
b: numberOrZero(event.axes?.b),
|
||||
c: numberOrZero(event.axes?.c),
|
||||
u: numberOrZero(event.axes?.u),
|
||||
v: numberOrZero(event.axes?.v),
|
||||
w: numberOrZero(event.axes?.w),
|
||||
plane: numberOrZero(event.axes?.arc?.plane),
|
||||
centerFirst: numberOrZero(event.axes?.arc?.centerFirst),
|
||||
centerSecond: numberOrZero(event.axes?.arc?.centerSecond),
|
||||
rotation: numberOrZero(event.axes?.arc?.rotation),
|
||||
axisEndPoint: numberOrZero(event.axes?.arc?.axisEndPoint),
|
||||
})),
|
||||
});
|
||||
return normalizeTimingResult(result, motion);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeTimingResult(result, motion) {
|
||||
const rawSegmentsByIndex = new Map((result.segments || []).map((segment) => [segment.index, segment]));
|
||||
let elapsedSeconds = 0;
|
||||
const segments = motion.map((event, index) => {
|
||||
const raw = rawSegmentsByIndex.get(index);
|
||||
const durationSeconds = Math.max(Number(raw?.durationSeconds) || 0, 0);
|
||||
elapsedSeconds += durationSeconds;
|
||||
return {
|
||||
index,
|
||||
line: event.line ?? null,
|
||||
type: event.type,
|
||||
motionClass: event.type === "STRAIGHT_TRAVERSE" ? "rapid" : "feed",
|
||||
durationSeconds,
|
||||
elapsedSeconds,
|
||||
currentVelocity: Number(raw?.velocity) || 0,
|
||||
velocityMmPerMin: (Number(raw?.velocity) || 0) * 60,
|
||||
queueDepth: Number(raw?.queueDepth) || 0,
|
||||
activeDepth: Number(raw?.activeDepth) || 0,
|
||||
cycles: Number(raw?.cycles) || 0,
|
||||
axes: event.axes || {},
|
||||
runtimeAxes: axesFromRuntimeRecord(raw, event.axes || {}),
|
||||
tp: raw || null,
|
||||
};
|
||||
});
|
||||
const rawSamples = Array.isArray(result.samples) && result.samples.length > 0
|
||||
? result.samples
|
||||
: synthesizeSamplesFromSegments(result.segments || []);
|
||||
const samples = normalizeSamples(rawSamples);
|
||||
|
||||
const feedSeconds = segments
|
||||
.filter((segment) => segment.motionClass === "feed")
|
||||
.reduce((total, segment) => total + segment.durationSeconds, 0);
|
||||
const rapidSeconds = segments
|
||||
.filter((segment) => segment.motionClass === "rapid")
|
||||
.reduce((total, segment) => total + segment.durationSeconds, 0);
|
||||
|
||||
return {
|
||||
apiName: "web-rtcp-5axis-linuxcnc-tp-program-timing",
|
||||
semanticBoundary: SEMANTIC_BOUNDARY,
|
||||
sourceBasis: "LinuxCNC interpreter canonical motion events queued through LinuxCNC src/emc/tp WASM",
|
||||
plannerRuntimeReady: result.ok === true
|
||||
&& result.addFailures === 0
|
||||
&& result.tpDone === 1
|
||||
&& segments.length === motion.length,
|
||||
sampleCount: Number(result.sampleCount) || samples.length,
|
||||
samples,
|
||||
totalSeconds: elapsedSeconds,
|
||||
totalMinutes: elapsedSeconds / 60,
|
||||
feedSeconds,
|
||||
rapidSeconds,
|
||||
motionCount: motion.length,
|
||||
emittedMotionCount: result.emittedMotionCount || 0,
|
||||
acceptedMotionCount: result.acceptedMotionCount || 0,
|
||||
totalCycles: result.totalCycles || 0,
|
||||
cycleTime: result.cycleTime || 0.001,
|
||||
addFailures: result.addFailures || 0,
|
||||
tpDone: result.tpDone === 1,
|
||||
finalQueueDepth: result.finalQueueDepth || 0,
|
||||
segments,
|
||||
raw: result,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSamples(samples) {
|
||||
return samples.map((sample, index) => ({
|
||||
sampleIndex: Number(sample.sampleIndex ?? index),
|
||||
cycle: Number(sample.cycle) || 0,
|
||||
timeSeconds: Number(sample.timeSeconds) || 0,
|
||||
motionIndex: Number(sample.motionIndex) || 0,
|
||||
line: Number.isFinite(Number(sample.line)) ? Number(sample.line) : null,
|
||||
type: sample.type || "-",
|
||||
axes: {
|
||||
x: numberOrZero(sample.x),
|
||||
y: numberOrZero(sample.y),
|
||||
z: numberOrZero(sample.z),
|
||||
a: numberOrZero(sample.a),
|
||||
b: numberOrZero(sample.b),
|
||||
c: numberOrZero(sample.c),
|
||||
u: numberOrZero(sample.u),
|
||||
v: numberOrZero(sample.v),
|
||||
w: numberOrZero(sample.w),
|
||||
},
|
||||
currentVelocity: numberOrZero(sample.currentVelocity),
|
||||
currentVelocityMmPerMin: numberOrZero(sample.currentVelocity) * 60,
|
||||
requestedVelocity: numberOrZero(sample.requestedVelocity),
|
||||
requestedVelocityMmPerMin: numberOrZero(sample.requestedVelocity) * 60,
|
||||
distanceToGo: numberOrZero(sample.distanceToGo),
|
||||
dtg: {
|
||||
x: numberOrZero(sample.dtgX),
|
||||
y: numberOrZero(sample.dtgY),
|
||||
z: numberOrZero(sample.dtgZ),
|
||||
},
|
||||
queueDepth: Number(sample.queueDepth) || 0,
|
||||
activeDepth: Number(sample.activeDepth) || 0,
|
||||
}));
|
||||
}
|
||||
|
||||
function synthesizeSamplesFromSegments(segments) {
|
||||
return segments.map((segment, index) => ({
|
||||
sampleIndex: index,
|
||||
cycle: Number(segment.cycles) || 0,
|
||||
timeSeconds: Number(segment.elapsedSeconds) || 0,
|
||||
motionIndex: Number(segment.index) || index,
|
||||
line: segment.line,
|
||||
type: segment.type,
|
||||
x: segment.x,
|
||||
y: segment.y,
|
||||
z: segment.z,
|
||||
a: segment.a,
|
||||
b: segment.b,
|
||||
c: segment.c,
|
||||
u: segment.u,
|
||||
v: segment.v,
|
||||
w: segment.w,
|
||||
currentVelocity: segment.velocity,
|
||||
requestedVelocity: segment.velocity,
|
||||
distanceToGo: segment.distanceToGo ?? 0,
|
||||
dtgX: segment.dtgX ?? 0,
|
||||
dtgY: segment.dtgY ?? 0,
|
||||
dtgZ: segment.dtgZ ?? 0,
|
||||
queueDepth: segment.queueDepth,
|
||||
activeDepth: segment.activeDepth,
|
||||
}));
|
||||
}
|
||||
|
||||
function axesFromRuntimeRecord(record, fallback = {}) {
|
||||
return {
|
||||
x: numberOrFallback(record?.x, fallback.x, 0),
|
||||
y: numberOrFallback(record?.y, fallback.y, 0),
|
||||
z: numberOrFallback(record?.z, fallback.z, 0),
|
||||
a: numberOrFallback(record?.a, fallback.a, 0),
|
||||
b: numberOrFallback(record?.b, fallback.b, 0),
|
||||
c: numberOrFallback(record?.c, fallback.c, 0),
|
||||
u: numberOrFallback(record?.u, fallback.u, 0),
|
||||
v: numberOrFallback(record?.v, fallback.v, 0),
|
||||
w: numberOrFallback(record?.w, fallback.w, 0),
|
||||
};
|
||||
}
|
||||
|
||||
function numberOrFallback(...values) {
|
||||
for (const value of values) {
|
||||
const number = Number(value);
|
||||
if (Number.isFinite(number)) return number;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function numberOrZero(value) {
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) ? number : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user