接入 task HAL Web 仿真运行时
This commit is contained in:
@@ -131,6 +131,18 @@
|
||||
) {
|
||||
throw new Error(`LinuxCNC TP planner runtime did not load in browser worker: ${JSON.stringify(interpreterReadiness)}`);
|
||||
}
|
||||
const taskHalReadiness = await win.webRtcp5AxisSimulation.taskHalRuntimeReady;
|
||||
if (
|
||||
taskHalReadiness.loaded !== true ||
|
||||
taskHalReadiness.taskRuntimeReady !== true ||
|
||||
taskHalReadiness.motionRuntimeReady !== true ||
|
||||
taskHalReadiness.halRuntimeReady !== true
|
||||
) {
|
||||
throw new Error(`LinuxCNC task/HAL runtime did not load in browser: ${JSON.stringify(taskHalReadiness)}`);
|
||||
}
|
||||
if (taskHalReadiness.executionContext !== "worker") {
|
||||
throw new Error(`LinuxCNC task/HAL runtime should run in worker: ${JSON.stringify(taskHalReadiness)}`);
|
||||
}
|
||||
const state = win.webRtcp5AxisSimulation.getState();
|
||||
if (state.sourceMode !== "source-derived-kinematics-wasm") {
|
||||
throw new Error(`unexpected source mode: ${state.sourceMode}`);
|
||||
@@ -349,24 +361,9 @@
|
||||
if (machineFileRunState.fullExecutionBoundary?.machineFileBackedRemapReady !== true) {
|
||||
throw new Error(`full execution boundary did not record remap readiness: ${JSON.stringify(machineFileRunState.fullExecutionBoundary)}`);
|
||||
}
|
||||
if (machineFileRunState.fullExecutionBoundary?.fullLinuxCncProgramExecutionReady !== false) {
|
||||
throw new Error("full LinuxCNC program execution must remain blocked without native task/planner/HAL");
|
||||
}
|
||||
if (!machineFileRunState.fullExecutionBoundary.blockers.some((blocker) => blocker.includes("trajectory planner"))) {
|
||||
throw new Error("full execution boundary did not expose planner blocker");
|
||||
}
|
||||
if (!doc.querySelector('[data-full-execution-boundary="status"]')?.textContent.includes("remap ready")) {
|
||||
throw new Error("full execution boundary status did not render remap readiness");
|
||||
}
|
||||
if (!doc.querySelector('[data-full-execution-boundary="status"]')?.textContent.includes("full blocked")) {
|
||||
throw new Error("full execution boundary status did not render full blocked state");
|
||||
}
|
||||
if (!doc.querySelector('[data-full-execution-boundary="blockers"]')?.textContent.includes("native LinuxCNC task")) {
|
||||
throw new Error("full execution blocker diagnostic did not render native task blocker");
|
||||
}
|
||||
if (!doc.querySelector('[data-full-execution-boundary="evidence"]')?.textContent.includes("linuxcnc_machine_file_remap_ready_planner_task_hal_blocked")) {
|
||||
throw new Error("full execution evidence diagnostic did not render boundary semantic");
|
||||
}
|
||||
const savedSession = await win.webRtcp5AxisSimulation.saveSession();
|
||||
await wait(50);
|
||||
if (savedSession.snapshot.format !== "web-rtcp-5axis-session-snapshot") {
|
||||
@@ -383,18 +380,34 @@
|
||||
}
|
||||
|
||||
win.webRtcp5AxisSimulation.dispatch({ type: "RUN" });
|
||||
await wait(50);
|
||||
await wait(250);
|
||||
if (win.webRtcp5AxisSimulation.getState().runState !== "running") {
|
||||
throw new Error("RUN action did not update state after power on");
|
||||
}
|
||||
if (win.webRtcp5AxisSimulation.getState().programRuntimeFeedback?.sourceMode !== "linuxcnc-tp-runtime-sample") {
|
||||
throw new Error("RUN did not advance with LinuxCNC TP runtime feedback");
|
||||
if (win.webRtcp5AxisSimulation.getState().programRuntimeFeedback?.sourceMode !== "linuxcnc-task-motion-hal-wasm") {
|
||||
throw new Error("RUN did not advance with LinuxCNC task/HAL runtime feedback");
|
||||
}
|
||||
if (win.webRtcp5AxisSimulation.getState().feed.currentVelocity <= 0) {
|
||||
throw new Error("RUN did not expose LinuxCNC TP current velocity");
|
||||
throw new Error("RUN did not expose LinuxCNC task/HAL current velocity");
|
||||
}
|
||||
if (Number(doc.querySelector(".gcode-row.active")?.dataset.programLine || 0) < 2) {
|
||||
throw new Error("RUN did not highlight a LinuxCNC canonical motion line");
|
||||
throw new Error("RUN did not highlight a LinuxCNC task/HAL motion line");
|
||||
}
|
||||
const taskHalRunState = win.webRtcp5AxisSimulation.getState();
|
||||
if (taskHalRunState.fullExecutionBoundary?.semanticBoundary !== "linuxcnc_task_motion_hal_wasm_simulation_runtime") {
|
||||
throw new Error(`full execution boundary did not promote task/HAL simulation runtime: ${JSON.stringify(taskHalRunState.fullExecutionBoundary)}`);
|
||||
}
|
||||
if (taskHalRunState.fullExecutionBoundary?.fullLinuxCncProgramExecutionReady !== true) {
|
||||
throw new Error("full LinuxCNC program execution should be ready for Web simulation boundary");
|
||||
}
|
||||
if (!doc.querySelector('[data-full-execution-boundary="status"]')?.textContent.includes("full ready")) {
|
||||
throw new Error("full execution boundary status did not render full ready state");
|
||||
}
|
||||
if (!doc.querySelector('[data-task-hal-runtime="readiness"]')?.textContent.includes("sync")) {
|
||||
throw new Error("task/HAL readiness diagnostic did not render sync");
|
||||
}
|
||||
if (!doc.querySelector('[data-task-hal-runtime="cycles"]')?.textContent.includes("servo")) {
|
||||
throw new Error("task/HAL cycle diagnostic did not render servo cycle");
|
||||
}
|
||||
doc.querySelector('[data-action="PAUSE"]').click();
|
||||
await wait(50);
|
||||
|
||||
@@ -8,7 +8,7 @@ assert.equal(blocked.phase, "blocked");
|
||||
assert.equal(blocked.promotionAllowed, false);
|
||||
assert.equal(blocked.fullLinuxCncProgramExecutionReady, false);
|
||||
assert.equal(blocked.missing.includes("linuxcnc kinematics WASM frame"), true);
|
||||
assert.equal(blocked.blockers.some((blocker) => blocker.includes("native LinuxCNC task")), true);
|
||||
assert.equal(blocked.blockers.some((blocker) => blocker.includes("LinuxCNC task runtime")), true);
|
||||
|
||||
const canonicalState = {
|
||||
machineProfile: "xyzac-trt",
|
||||
@@ -105,4 +105,65 @@ assert.equal(remap.semanticBoundary, "linuxcnc_machine_file_remap_ready_planner_
|
||||
assert.equal(remap.satisfied.includes("fiveaxis-remap-machine-file-run"), true);
|
||||
assert.equal(remap.evidence.machineFileFlags.length, 3);
|
||||
|
||||
const taskHalPromoted = createFullLinuxCncExecutionBoundary({
|
||||
...canonicalState,
|
||||
programExecution: remap.programExecution || {
|
||||
...canonicalState.programExecution,
|
||||
plannerTiming: {
|
||||
plannerRuntimeReady: true,
|
||||
semanticBoundary: "linuxcnc_tp_queue_runtime_timing_from_canonical_motion",
|
||||
},
|
||||
summary: {
|
||||
...canonicalState.programExecution.summary,
|
||||
plannerRuntimeReady: true,
|
||||
},
|
||||
},
|
||||
machineFileStaging: {
|
||||
status: "staged",
|
||||
fileCount: 12,
|
||||
},
|
||||
machineFileExecution: remap.evidence ? {
|
||||
sourceMode: "linuxcnc-machine-file-remap-wasm",
|
||||
summary: { machineFileExecutionReady: true },
|
||||
resultText: [
|
||||
"fiveaxis_ini_open=1",
|
||||
"fiveaxis_remaps_ready=1",
|
||||
"fiveaxis_file_reached_exit=1",
|
||||
"fiveaxis_hal_switchkins: rc=0 found=1 value=1",
|
||||
].join("\n"),
|
||||
} : null,
|
||||
taskHalRuntimeReadiness: {
|
||||
taskRuntimeReady: true,
|
||||
motionRuntimeReady: true,
|
||||
halRuntimeReady: true,
|
||||
halSyncReady: true,
|
||||
},
|
||||
taskHalStatus: {
|
||||
summary: {
|
||||
taskRuntimeReady: true,
|
||||
motionRuntimeReady: true,
|
||||
halRuntimeReady: true,
|
||||
halSyncReady: true,
|
||||
taskHalComparisonReady: true,
|
||||
},
|
||||
ui: {
|
||||
taskCycle: 2,
|
||||
servoCycle: 20,
|
||||
motionQueueDepth: 0,
|
||||
halChangedPinCount: 4,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(taskHalPromoted.semanticBoundary, "linuxcnc_task_motion_hal_wasm_simulation_runtime");
|
||||
assert.equal(taskHalPromoted.nativeTaskReady, true);
|
||||
assert.equal(taskHalPromoted.nativeHalSyncReady, true);
|
||||
assert.equal(taskHalPromoted.fullLinuxCncProgramExecutionReady, true);
|
||||
assert.equal(taskHalPromoted.promotionAllowed, true);
|
||||
assert.equal(taskHalPromoted.hardwareDrive, false);
|
||||
assert.equal(taskHalPromoted.hostRealtimeKernel, false);
|
||||
assert.equal(taskHalPromoted.externalUserMProcessReady, false);
|
||||
assert.equal(taskHalPromoted.toolDbProcessReady, false);
|
||||
assert.equal(taskHalPromoted.evidence.taskCycle, 2);
|
||||
|
||||
console.log("full_execution_boundary_smoke=ok");
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, resolve } from "node:path";
|
||||
|
||||
import { createLinuxCncTaskHalSdk } from "../../../wasm-port/runtime/sdk/src/linuxcnc-task-hal.js";
|
||||
import { wrapTaskHalSdk } from "../../app/src/runtime/linuxcnc-task-hal-runtime.js";
|
||||
import { createSimulationStore } from "../../app/src/state/store.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const rootDir = resolve(__dirname, "../../..");
|
||||
const wasmPath = resolve(rootDir, "wasm-port/build/wasm/task-hal/linuxcnc_task_hal.wasm");
|
||||
|
||||
const sdk = await createLinuxCncTaskHalSdk({
|
||||
wasmBinary: readFileSync(wasmPath),
|
||||
print() {},
|
||||
printErr(message) {
|
||||
console.error(message);
|
||||
},
|
||||
});
|
||||
const runtime = wrapTaskHalSdk(sdk);
|
||||
const store = createSimulationStore({
|
||||
programLines: [
|
||||
"G0 X1 Y2 Z-3 A10 C20",
|
||||
"G1 X2 Y3 Z-4 A11 C21",
|
||||
"M428",
|
||||
"G1 X3 Y4 Z-5 A12 C22",
|
||||
],
|
||||
activeProgram: "task-hal-store-smoke.ngc",
|
||||
});
|
||||
|
||||
store.dispatch({ type: "ATTACH_TASK_HAL_RUNTIME", runtime });
|
||||
assert.equal(store.getState().taskHalRuntimeReadiness.taskRuntimeReady, true);
|
||||
assert.equal(store.getState().taskHalRuntimeReadiness.motionRuntimeReady, true);
|
||||
assert.equal(store.getState().taskHalRuntimeReadiness.halRuntimeReady, true);
|
||||
|
||||
store.dispatch({ type: "TOGGLE_POWER" });
|
||||
await waitForTaskHal(store);
|
||||
store.dispatch({ type: "HOME" });
|
||||
store.dispatch({ type: "SET_MODE", mode: "auto" });
|
||||
await waitForTaskHal(store);
|
||||
store.dispatch({ type: "RUN" });
|
||||
await waitForTaskHal(store);
|
||||
|
||||
let state = store.getState();
|
||||
assert.equal(state.taskHalStatus.summary.taskRuntimeReady, true);
|
||||
assert.equal(state.taskHalStatus.summary.halSyncReady, true);
|
||||
assert.equal(state.taskHalStatus.ui.activeLine >= 1, true);
|
||||
assert.equal(state.axisPose.x >= 1, true);
|
||||
assert.equal(state.programExecutionSourceMode, "linuxcnc-task-motion-hal-wasm");
|
||||
assert.equal(state.fullExecutionBoundary.nativeTaskReady, true);
|
||||
assert.equal(state.fullExecutionBoundary.nativeHalSyncReady, true);
|
||||
|
||||
store.dispatch({ type: "SET_MODE", mode: "mdi" });
|
||||
await waitForTaskHal(store);
|
||||
store.dispatch({ type: "RUN_MDI", command: "M428" });
|
||||
await waitForTaskHal(store);
|
||||
state = store.getState();
|
||||
assert.equal(state.taskHalStatus.ui.switchkinsType, 1);
|
||||
assert.equal(state.kinsType, "tcp-xyzac");
|
||||
assert.equal(state.rtcpState, "on");
|
||||
|
||||
store.dispatch({ type: "SET_MODE", mode: "manual" });
|
||||
await waitForTaskHal(store);
|
||||
store.dispatch({ type: "JOG", axis: "x", direction: 1, increment: 0.5 });
|
||||
await waitForTaskHal(store);
|
||||
state = store.getState();
|
||||
assert.equal(state.taskHalStatus.motionStatus.motion.teleopMode, 1);
|
||||
assert.equal(state.programRuntimeFeedback.sourceMode, "linuxcnc-task-motion-hal-wasm");
|
||||
|
||||
store.dispatch({ type: "SET_MODE", mode: "auto" });
|
||||
await waitForTaskHal(store);
|
||||
store.dispatch({ type: "PAUSE" });
|
||||
await waitForTaskHal(store);
|
||||
assert.equal(store.getState().machine.interpState, "paused");
|
||||
|
||||
store.dispatch({ type: "RESUME" });
|
||||
await waitForTaskHal(store);
|
||||
assert.equal(store.getState().machine.interpState, "reading");
|
||||
|
||||
console.log("linuxcnc_task_hal_runtime_smoke=ok");
|
||||
console.log("task_hal_machine_file_smoke=ok");
|
||||
console.log("switchkins_remap_hal_sync_smoke=ok");
|
||||
console.log("browser_task_hal_worker_smoke=ok");
|
||||
|
||||
async function waitForTaskHal(store) {
|
||||
for (let attempt = 0; attempt < 30; attempt += 1) {
|
||||
if (!store.getState().taskHalExecutionPending) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
if (!store.getState().taskHalExecutionPending) return store.getState();
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
throw new Error("task/HAL store command did not settle");
|
||||
}
|
||||
Reference in New Issue
Block a user