接入 task HAL Web 仿真运行时
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
const DEFAULT_SDK_MODULE_URLS = [
|
||||
new URL("../../../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js", import.meta.url).href,
|
||||
new URL("../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js", import.meta.url).href,
|
||||
];
|
||||
|
||||
const SEMANTIC_BOUNDARY = "linuxcnc_task_motion_hal_wasm_simulation_runtime";
|
||||
|
||||
export async function createLinuxCncTaskHalRuntime({
|
||||
sdkModuleUrl = null,
|
||||
moduleOptions = {},
|
||||
} = {}) {
|
||||
const errors = [];
|
||||
const candidateUrls = sdkModuleUrl ? [sdkModuleUrl] : DEFAULT_SDK_MODULE_URLS;
|
||||
for (const url of candidateUrls) {
|
||||
try {
|
||||
const { createLinuxCncTaskHalSdk } = await import(url);
|
||||
const sdk = await createLinuxCncTaskHalSdk(moduleOptions);
|
||||
return wrapTaskHalSdk(sdk, {
|
||||
sdkModuleUrl: url,
|
||||
executionContext: "direct",
|
||||
});
|
||||
} catch (error) {
|
||||
errors.push(`${url}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
throw new Error(`LinuxCNC task/HAL runtime unavailable: ${errors.join(" | ")}`);
|
||||
}
|
||||
|
||||
export function wrapTaskHalSdk(sdk, {
|
||||
sdkModuleUrl = null,
|
||||
executionContext = "direct",
|
||||
workerUrl = null,
|
||||
} = {}) {
|
||||
if (!sdk || typeof sdk.readiness !== "function") {
|
||||
throw new Error("wrapTaskHalSdk requires a task/HAL SDK");
|
||||
}
|
||||
|
||||
return {
|
||||
apiName: "web-rtcp-5axis-linuxcnc-task-hal-runtime",
|
||||
semanticBoundary: SEMANTIC_BOUNDARY,
|
||||
executionContext,
|
||||
sdkModuleUrl,
|
||||
workerUrl,
|
||||
loaded: true,
|
||||
|
||||
readiness() {
|
||||
const readiness = sdk.readiness();
|
||||
const taskRuntimeReady = readiness.taskRuntimeReady === true;
|
||||
const motionRuntimeReady = readiness.motionRuntimeReady === true;
|
||||
const halRuntimeReady = readiness.halRuntimeReady === true;
|
||||
return {
|
||||
apiName: "web-rtcp-5axis-linuxcnc-task-hal-runtime-readiness",
|
||||
loaded: true,
|
||||
semanticBoundary: SEMANTIC_BOUNDARY,
|
||||
sdkSemanticBoundary: readiness.semanticBoundary,
|
||||
executionContext,
|
||||
workerUrl,
|
||||
taskRuntimeReady,
|
||||
motionRuntimeReady,
|
||||
halRuntimeReady,
|
||||
halSyncReady: taskRuntimeReady && motionRuntimeReady && halRuntimeReady,
|
||||
nativeTaskReady: taskRuntimeReady,
|
||||
nativeHalSyncReady: taskRuntimeReady && motionRuntimeReady && halRuntimeReady,
|
||||
hardwareDrive: false,
|
||||
hostRealtimeKernel: false,
|
||||
externalUserMProcessReady: false,
|
||||
};
|
||||
},
|
||||
|
||||
initSession(session = {}) {
|
||||
return sdk.initSession(session);
|
||||
},
|
||||
|
||||
stageFiles(files = []) {
|
||||
let staged = 0;
|
||||
for (const file of files) {
|
||||
const path = file.wasmPath || file.path;
|
||||
if (!path) continue;
|
||||
const rc = sdk.stageFile(path, file.text || "");
|
||||
if (rc !== 0) {
|
||||
throw new Error(`lctask_stage_file failed for ${path} rc=${rc}`);
|
||||
}
|
||||
staged += 1;
|
||||
}
|
||||
return staged;
|
||||
},
|
||||
|
||||
openProgram(path) {
|
||||
const rc = sdk.openProgram(path);
|
||||
if (rc !== 0) {
|
||||
throw new Error(`lctask_open_program failed for ${path} rc=${rc}`);
|
||||
}
|
||||
return rc;
|
||||
},
|
||||
|
||||
sendCommand(command) {
|
||||
const rc = sdk.sendCommand(command);
|
||||
if (rc !== 0) {
|
||||
throw new Error(`lctask_send_command_json failed for ${command?.type || "unknown"} rc=${rc}`);
|
||||
}
|
||||
return rc;
|
||||
},
|
||||
|
||||
runCycles(options = {}) {
|
||||
const rc = sdk.runCycles(options);
|
||||
if (rc !== 0) {
|
||||
throw new Error(`lctask_run_cycles failed rc=${rc}`);
|
||||
}
|
||||
return rc;
|
||||
},
|
||||
|
||||
readStatus() {
|
||||
return normalizeTaskHalStatus(sdk.readStatus());
|
||||
},
|
||||
|
||||
readEvents() {
|
||||
return sdk.readEvents();
|
||||
},
|
||||
|
||||
resetSession() {
|
||||
return sdk.resetSession();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function buildTaskHalSessionFromMachineFiles({ profile, plan, save, selectedProgramRel = null } = {}) {
|
||||
const files = save?.files || [];
|
||||
const iniFile = files.find((file) => file.kind === "ini")
|
||||
|| files.find((file) => file.sourceRel === profile?.iniPath)
|
||||
|| null;
|
||||
const selectedFile = selectedProgramRel
|
||||
? files.find((file) => file.sourceRel === selectedProgramRel)
|
||||
: null;
|
||||
const programFile = selectedFile
|
||||
|| files.find((file) => file.wasmPath === plan?.wasmProgramPath)
|
||||
|| files.find((file) => file.kind === "demo")
|
||||
|| null;
|
||||
|
||||
return {
|
||||
apiName: "web-rtcp-5axis-task-hal-session",
|
||||
semanticBoundary: "linuxcnc_machine_files_for_task_hal_wasm_runtime",
|
||||
profileId: profile?.id || plan?.profileId || save?.profileId || "unknown",
|
||||
iniPath: iniFile?.wasmPath || plan?.wasmIniPath || plan?.iniPath || profile?.iniPath || null,
|
||||
iniText: iniFile?.text || "",
|
||||
programPath: programFile?.wasmPath || plan?.wasmProgramPath || null,
|
||||
programSourceRel: programFile?.sourceRel || selectedProgramRel || null,
|
||||
halFiles: files.filter((file) => file.kind === "hal").map(sessionFileDescriptor),
|
||||
toolTableFiles: files.filter((file) => file.kind === "toolTable").map(sessionFileDescriptor),
|
||||
remapFiles: files.filter((file) => file.kind === "remap").map(sessionFileDescriptor),
|
||||
files: files.map(sessionFileDescriptor),
|
||||
fileCount: files.length,
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeTaskHalStatus(status = {}) {
|
||||
const motion = status.motionStatus?.motion || {};
|
||||
const axis = status.motionStatus?.axis || {};
|
||||
const halPins = status.halSnapshot?.pins || {};
|
||||
return {
|
||||
...status,
|
||||
semanticBoundary: SEMANTIC_BOUNDARY,
|
||||
summary: {
|
||||
taskRuntimeReady: status.taskRuntimeReady === true,
|
||||
motionRuntimeReady: status.motionStatus?.motionHalSyncReady === true || status.taskCommandsDriveMotionRuntime === true,
|
||||
halRuntimeReady: Boolean(status.halSnapshot?.halRuntimeReady ?? status.halSnapshot?.ready ?? true),
|
||||
halSyncReady: status.taskCommandsDriveMotionRuntime === true && Boolean(halPins["motion.program-line"]),
|
||||
taskHalComparisonReady: status.taskRuntimeReady === true && status.taskCommandsDriveMotionRuntime === true,
|
||||
switchkinsRemapHalSync: Boolean(halPins["motion.switchkins-type"]),
|
||||
nativeTaskReady: status.taskRuntimeReady === true,
|
||||
nativeHalSyncReady: status.taskCommandsDriveMotionRuntime === true && Boolean(halPins["motion.program-line"]),
|
||||
fullLinuxCncProgramExecutionReady: false,
|
||||
hardwareDrive: false,
|
||||
hostRealtimeKernel: false,
|
||||
},
|
||||
ui: {
|
||||
taskState: String(status.task?.state || "ESTOP").toLowerCase(),
|
||||
taskMode: String(status.task?.mode || "MANUAL").toLowerCase(),
|
||||
interpState: String(status.task?.interpState || "IDLE").toLowerCase(),
|
||||
execState: String(status.task?.execState || "DONE").toLowerCase(),
|
||||
taskCycle: Number(status.task?.taskCycle ?? status.taskCycle ?? 0),
|
||||
servoCycle: Number(status.motionStatus?.cycle ?? status.task?.servoCycle ?? 0),
|
||||
motionQueueDepth: Number(status.motionStatus?.queueDepth ?? motion.queueDepth ?? 0),
|
||||
halChangedPinCount: Array.isArray(status.halSnapshot?.changedPins)
|
||||
? status.halSnapshot.changedPins.length
|
||||
: Number(status.halSnapshot?.changedPinCount || 0),
|
||||
activeLine: Number(motion.programLine || halPins["motion.program-line"]?.value || 1),
|
||||
switchkinsType: Number(motion.switchkinsType ?? halPins["motion.switchkins-type"]?.value ?? 0),
|
||||
axisPose: {
|
||||
x: Number(axis.x ?? halPins["axis.0.pos-cmd"]?.value ?? 0),
|
||||
y: Number(axis.y ?? halPins["axis.1.pos-cmd"]?.value ?? 0),
|
||||
z: Number(axis.z ?? halPins["axis.2.pos-cmd"]?.value ?? 0),
|
||||
a: Number(axis.a ?? halPins["axis.3.pos-cmd"]?.value ?? 0),
|
||||
b: Number(axis.b ?? halPins["axis.4.pos-cmd"]?.value ?? 0),
|
||||
c: Number(axis.c ?? halPins["axis.5.pos-cmd"]?.value ?? 0),
|
||||
},
|
||||
currentVelocity: Number(motion.currentVel || motion.currentVelocity || 0) * 60,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function sessionFileDescriptor(file) {
|
||||
return {
|
||||
sourceRel: file.sourceRel,
|
||||
wasmPath: file.wasmPath || file.path,
|
||||
path: file.wasmPath || file.path,
|
||||
kind: file.kind,
|
||||
bytes: Number(file.bytes || String(file.text || "").length),
|
||||
text: file.text || "",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user