接入虚拟HAL会话保存恢复
结论:real simulation page 已提供显式 Save/Restore HAL Session 按钮与 browser API,使用 OPFS session snapshot 保存和恢复 virtual HAL runtime state,并随 payload 校验 source compliance、sim-config coverage、halcmd fixtures 与 motion controller matrix,证明虚拟HAL可恢复地满足Web方式数控系统仿真范围。
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
import {
|
||||
cloneVirtualHalState,
|
||||
createVirtualHalCommandScriptFixtureReport,
|
||||
createVirtualHalMotionControllerMatrixReport,
|
||||
createVirtualHalSimConfigSourceCoverageReport,
|
||||
createVirtualHalSimulationReplacementReport,
|
||||
createVirtualHalSourceComplianceReport,
|
||||
@@ -66,6 +67,7 @@ function validateVirtualHalSessionPayload(virtualHal) {
|
||||
assertPlainObject(virtualHal.sourceCompliance, "virtual HAL session source compliance");
|
||||
assertPlainObject(virtualHal.simConfigSourceCoverage, "virtual HAL session sim-config source coverage");
|
||||
assertPlainObject(virtualHal.commandScriptFixtures, "virtual HAL session command script fixtures");
|
||||
assertPlainObject(virtualHal.motionControllerMatrix, "virtual HAL session motion controller matrix");
|
||||
if (virtualHal.state.source !== "browser-virtual-hal") {
|
||||
throw new Error(`Unsupported virtual HAL state source: ${virtualHal.state.source}`);
|
||||
}
|
||||
@@ -78,6 +80,9 @@ function validateVirtualHalSessionPayload(virtualHal) {
|
||||
if (virtualHal.commandScriptFixtures.complete !== true) {
|
||||
throw new Error("virtual HAL command script fixtures are incomplete.");
|
||||
}
|
||||
if (virtualHal.motionControllerMatrix.complete !== true) {
|
||||
throw new Error("virtual HAL motion controller matrix is incomplete.");
|
||||
}
|
||||
return virtualHal;
|
||||
}
|
||||
|
||||
@@ -87,6 +92,7 @@ export function createVirtualHalSessionPayload(halState = createVirtualHalState(
|
||||
const sourceCompliance = options.sourceCompliance ?? createVirtualHalSourceComplianceReport({ halState: state });
|
||||
const simConfigSourceCoverage = options.simConfigSourceCoverage ?? createVirtualHalSimConfigSourceCoverageReport(options);
|
||||
const commandScriptFixtures = options.commandScriptFixtures ?? createVirtualHalCommandScriptFixtureReport({ ...options, halState: state });
|
||||
const motionControllerMatrix = options.motionControllerMatrix ?? createVirtualHalMotionControllerMatrixReport(options);
|
||||
return validateVirtualHalSessionPayload({
|
||||
apiName: "linuxcnc-wasm-virtual-hal-session-payload",
|
||||
payloadVersion: 1,
|
||||
@@ -96,6 +102,7 @@ export function createVirtualHalSessionPayload(halState = createVirtualHalState(
|
||||
sourceCompliance,
|
||||
simConfigSourceCoverage,
|
||||
commandScriptFixtures,
|
||||
motionControllerMatrix,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -486,6 +486,10 @@ private module paths:
|
||||
`saveSessionSnapshot()`, `saveMachineSessionSnapshot()`,
|
||||
`loadMachineSessionSnapshot()`, and `validateSessionSnapshot()` for session
|
||||
snapshot persistence.
|
||||
- `createVirtualHalSessionPayload()` and
|
||||
`restoreVirtualHalStateFromSessionSnapshot()` for saving and restoring Web
|
||||
virtual HAL state with source compliance, sim-config coverage, halcmd
|
||||
fixtures, and motion controller matrix evidence.
|
||||
- `machineFilePaths()`, `saveMachineTextFiles()`, `loadMachineTextFiles()`, and
|
||||
`gcodeFilenameFromProgramPath()` for machine-file and G-code file helpers.
|
||||
- `readMachineSessionReadiness()` for checking persisted INI, parameter,
|
||||
|
||||
@@ -1474,6 +1474,8 @@
|
||||
<div class="machine-session-actions">
|
||||
<button type="button" data-check-machine-session>Check Session</button>
|
||||
<button type="button" data-load-machine-session>Load Session</button>
|
||||
<button type="button" data-save-virtual-hal-session>Save HAL Session</button>
|
||||
<button type="button" data-restore-virtual-hal-session>Restore HAL Session</button>
|
||||
<label><input type="checkbox" checked data-use-loaded-session> Use Session</label>
|
||||
</div>
|
||||
<div class="machine-session-status" data-machine-session-status>
|
||||
@@ -1484,6 +1486,7 @@
|
||||
<span>Loaded INI: <span data-machine-session-loaded-path="ini">-</span></span>
|
||||
<span>Loaded parameters: <span data-machine-session-loaded-path="parameters">-</span></span>
|
||||
<span>Loaded tool table: <span data-machine-session-loaded-path="toolTable">-</span></span>
|
||||
<span>Virtual HAL session: <span data-virtual-hal-session-status>not saved</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="active-codes" data-axis-shell="active-codes">
|
||||
@@ -1781,6 +1784,13 @@ M2
|
||||
import {
|
||||
loadMachineSessionFromOpfs,
|
||||
} from "../../opfs/linuxcnc-machine-session-bridge.js";
|
||||
import {
|
||||
createMachineSessionSnapshotPayload,
|
||||
createVirtualHalSessionPayload,
|
||||
loadSessionSnapshot,
|
||||
restoreVirtualHalStateFromSessionSnapshot,
|
||||
saveSessionSnapshot,
|
||||
} from "../../opfs/snapshot-store.js";
|
||||
import {
|
||||
readMachineSessionReadiness,
|
||||
} from "../../opfs/machine-session-readiness.js";
|
||||
@@ -1842,6 +1852,8 @@ M2
|
||||
const machineSessionGcodeInput = document.querySelector("[data-machine-session-gcode]");
|
||||
const checkMachineSessionButton = document.querySelector("[data-check-machine-session]");
|
||||
const loadMachineSessionButton = document.querySelector("[data-load-machine-session]");
|
||||
const saveVirtualHalSessionButton = document.querySelector("[data-save-virtual-hal-session]");
|
||||
const restoreVirtualHalSessionButton = document.querySelector("[data-restore-virtual-hal-session]");
|
||||
const useLoadedSessionInput = document.querySelector("[data-use-loaded-session]");
|
||||
const resetButton = document.querySelector("[data-playback-reset]");
|
||||
const prevButton = document.querySelector("[data-playback-prev]");
|
||||
@@ -1900,6 +1912,16 @@ M2
|
||||
opfsPath: "linuxcnc/gcode/axis-editor.ngc",
|
||||
status: "OPFS program not saved",
|
||||
};
|
||||
let virtualHalSessionState = {
|
||||
ready: false,
|
||||
phase: "not-saved",
|
||||
sessionId: "axis-browser-session",
|
||||
filename: "virtual-hal-session.json",
|
||||
machineId: "axis-browser-sim",
|
||||
status: "Virtual HAL session not saved",
|
||||
revision: 0,
|
||||
restored: false,
|
||||
};
|
||||
let machineReadinessState = {
|
||||
machineId: "axis-browser-sim",
|
||||
ready: false,
|
||||
@@ -2644,6 +2666,17 @@ M2
|
||||
return { ...opfsProgramState };
|
||||
}
|
||||
|
||||
function setVirtualHalSessionState(nextState) {
|
||||
virtualHalSessionState = {
|
||||
...virtualHalSessionState,
|
||||
...nextState,
|
||||
};
|
||||
document.body.dataset.virtualHalSessionPhase = virtualHalSessionState.phase;
|
||||
document.body.dataset.virtualHalSessionReady = virtualHalSessionState.ready ? "true" : "false";
|
||||
setAllText("[data-virtual-hal-session-status]", virtualHalSessionState.status);
|
||||
return { ...virtualHalSessionState };
|
||||
}
|
||||
|
||||
function renderMachineReadiness(readiness) {
|
||||
machineReadinessState = readiness;
|
||||
document.body.dataset.machineSessionReady = readiness.ready ? "true" : "false";
|
||||
@@ -3489,6 +3522,69 @@ M2
|
||||
return state;
|
||||
}
|
||||
|
||||
function virtualHalSessionOptions(options = {}) {
|
||||
const sessionId = String(options.sessionId ?? machineSessionSnapshotInput?.value ?? "axis-browser-session").trim() || "axis-browser-session";
|
||||
const machineId = String(options.machineId ?? machineSessionIdInput?.value ?? "axis-browser-sim").trim() || "axis-browser-sim";
|
||||
const filename = String(options.filename ?? options.snapshotFilename ?? "virtual-hal-session.json").trim() || "virtual-hal-session.json";
|
||||
const gcodeFilename = String(options.gcodeFilename ?? machineSessionGcodeInput?.value ?? "").trim();
|
||||
return {
|
||||
sessionId,
|
||||
machineId,
|
||||
filename,
|
||||
...(gcodeFilename ? { gcodeFilename } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
async function saveVirtualHalSessionSnapshot(options = {}) {
|
||||
const session = virtualHalSessionOptions(options);
|
||||
const virtualHal = createVirtualHalSessionPayload(virtualHalState);
|
||||
const payload = createMachineSessionSnapshotPayload(session.machineId, {
|
||||
...(session.gcodeFilename ? { gcodeFilename: session.gcodeFilename } : {}),
|
||||
virtualHal,
|
||||
});
|
||||
const snapshot = await saveSessionSnapshot(session.sessionId, payload, {
|
||||
filename: session.filename,
|
||||
metadata: {
|
||||
source: "real-browser-simulation-virtual-hal-session",
|
||||
virtualHalRevision: virtualHal.state.revision,
|
||||
},
|
||||
});
|
||||
pushStatusHistory("hal-session", `saved ${session.sessionId}/${session.filename}`);
|
||||
return setVirtualHalSessionState({
|
||||
ready: true,
|
||||
phase: "saved",
|
||||
sessionId: session.sessionId,
|
||||
machineId: session.machineId,
|
||||
filename: session.filename,
|
||||
status: `Saved HAL session: linuxcnc/sessions/${session.sessionId}/${session.filename}`,
|
||||
revision: virtualHal.state.revision,
|
||||
restored: false,
|
||||
snapshot,
|
||||
});
|
||||
}
|
||||
|
||||
async function restoreVirtualHalSessionSnapshot(options = {}) {
|
||||
const session = virtualHalSessionOptions(options);
|
||||
const snapshot = await loadSessionSnapshot(session.sessionId, {
|
||||
filename: session.filename,
|
||||
});
|
||||
virtualHalState = restoreVirtualHalStateFromSessionSnapshot(snapshot);
|
||||
renderVirtualHalState();
|
||||
renderAxisStatusbar();
|
||||
pushStatusHistory("hal-session", `restored ${session.sessionId}/${session.filename}`);
|
||||
return setVirtualHalSessionState({
|
||||
ready: true,
|
||||
phase: "restored",
|
||||
sessionId: session.sessionId,
|
||||
machineId: snapshot.payload?.machineId ?? session.machineId,
|
||||
filename: session.filename,
|
||||
status: `Restored HAL session: linuxcnc/sessions/${session.sessionId}/${session.filename}`,
|
||||
revision: virtualHalState.revision,
|
||||
restored: true,
|
||||
snapshot,
|
||||
});
|
||||
}
|
||||
|
||||
async function checkMachineSessionReadiness(options = {}) {
|
||||
const machineId = String(options.machineId ?? machineSessionIdInput?.value ?? "axis-browser-sim").trim() || "axis-browser-sim";
|
||||
const sessionId = String(options.sessionId ?? machineSessionSnapshotInput?.value ?? "").trim();
|
||||
@@ -3876,6 +3972,9 @@ M2
|
||||
checkMachineSessionReadiness,
|
||||
getMachineSessionLoadState: () => machineSessionLoadState,
|
||||
loadReadyMachineSession,
|
||||
getVirtualHalSessionState: () => ({ ...virtualHalSessionState }),
|
||||
saveVirtualHalSessionSnapshot,
|
||||
restoreVirtualHalSessionSnapshot,
|
||||
getRunMode: () => syncRunMode(),
|
||||
getRunControlState: () => renderRunControlState(),
|
||||
getAxisStatusbarState: () => renderAxisStatusbar(),
|
||||
@@ -4018,6 +4117,26 @@ M2
|
||||
});
|
||||
});
|
||||
});
|
||||
saveVirtualHalSessionButton?.addEventListener("click", () => {
|
||||
window.linuxCncRealSimulationApi.saveVirtualHalSessionSnapshot().catch((error) => {
|
||||
setVirtualHalSessionState({
|
||||
ready: false,
|
||||
phase: "failed",
|
||||
status: `HAL session save failed: ${error.message}`,
|
||||
});
|
||||
recordError(error, "hal session save");
|
||||
});
|
||||
});
|
||||
restoreVirtualHalSessionButton?.addEventListener("click", () => {
|
||||
window.linuxCncRealSimulationApi.restoreVirtualHalSessionSnapshot().catch((error) => {
|
||||
setVirtualHalSessionState({
|
||||
ready: false,
|
||||
phase: "failed",
|
||||
status: `HAL session restore failed: ${error.message}`,
|
||||
});
|
||||
recordError(error, "hal session restore");
|
||||
});
|
||||
});
|
||||
useLoadedSessionInput?.addEventListener("change", () => {
|
||||
setUseLoadedSession(useLoadedSessionInput.checked);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user