接入虚拟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:
84
text17.txt
84
text17.txt
@@ -866,3 +866,87 @@ git diff --check ok
|
|||||||
让 release readiness 页面能显示 browser diagnostics validation rows。
|
让 release readiness 页面能显示 browser diagnostics validation rows。
|
||||||
3. 扩展 motion matrix 与 LinuxCNC sim configs 的关联,把 external-offsets、QtDragon
|
3. 扩展 motion matrix 与 LinuxCNC sim configs 的关联,把 external-offsets、QtDragon
|
||||||
multi-joint、vismach remap sims 的 motion source evidence 显式映射到 matrix fixture rows。
|
multi-joint、vismach remap sims 的 motion source evidence 显式映射到 matrix fixture rows。
|
||||||
|
|
||||||
|
### 九、2026-06-17 Real Simulation Virtual HAL Session Save/Restore
|
||||||
|
|
||||||
|
继续按本文件铁律推进:虚拟 HAL 的功能完全来源于 LinuxCNC 源程序,并且虚拟 HAL
|
||||||
|
完全满足 Web 方式数控系统仿真系统。本批完成上一节下一步第 1 项,把 virtual HAL
|
||||||
|
session payload 接入 real simulation page 的显式保存/恢复按钮与 browser API,让用户可在
|
||||||
|
Web CNC 仿真页面直接保存和恢复完整 virtual HAL runtime state。
|
||||||
|
|
||||||
|
新增实质能力:
|
||||||
|
|
||||||
|
- `runtime/opfs/snapshot-store.js` 扩展 virtual HAL session payload:
|
||||||
|
- 新增 `motionControllerMatrix`;
|
||||||
|
- payload validation 要求 motion controller matrix complete;
|
||||||
|
- `createVirtualHalSessionPayload()` 默认生成 motion matrix evidence;
|
||||||
|
- real simulation page 新增 Machine Session 面板控件:
|
||||||
|
- `Save HAL Session`;
|
||||||
|
- `Restore HAL Session`;
|
||||||
|
- `Virtual HAL session` 状态行;
|
||||||
|
- Browser simulation API 新增:
|
||||||
|
- `getVirtualHalSessionState()`;
|
||||||
|
- `saveVirtualHalSessionSnapshot(options)`;
|
||||||
|
- `restoreVirtualHalSessionSnapshot(options)`;
|
||||||
|
- 保存流程:
|
||||||
|
- 使用当前 `virtualHalState` 创建 `createVirtualHalSessionPayload()`;
|
||||||
|
- 使用 `createMachineSessionSnapshotPayload()` 封装 machine/session payload;
|
||||||
|
- 使用 `saveSessionSnapshot()` 写入 OPFS;
|
||||||
|
- snapshot metadata 记录 `real-browser-simulation-virtual-hal-session` 与 HAL revision;
|
||||||
|
- 恢复流程:
|
||||||
|
- 使用 `loadSessionSnapshot()` 读取 OPFS snapshot;
|
||||||
|
- 使用 `restoreVirtualHalStateFromSessionSnapshot()` 恢复 virtual HAL state;
|
||||||
|
- 重新渲染 HAL machine state、statusbar 和 session 状态;
|
||||||
|
- SDK README OPFS/session persistence section 同步说明:
|
||||||
|
- `createVirtualHalSessionPayload()`;
|
||||||
|
- `restoreVirtualHalStateFromSessionSnapshot()`;
|
||||||
|
- payload 包含 source compliance、sim-config coverage、halcmd fixtures、motion
|
||||||
|
controller matrix evidence。
|
||||||
|
|
||||||
|
新增/扩展验证:
|
||||||
|
|
||||||
|
- `verify_file_service.mjs` 覆盖:
|
||||||
|
- virtual HAL session payload 内含 motion controller matrix complete;
|
||||||
|
- machine session snapshot payload 内含 motion matrix LinuxCNC source evidence;
|
||||||
|
- `verify_sdk_surface.mjs` 覆盖:
|
||||||
|
- virtual HAL session payload 内含 motion controller matrix complete;
|
||||||
|
- `real_simulation_page_smoke.html` 覆盖:
|
||||||
|
- 页面存在显式保存/恢复按钮;
|
||||||
|
- browser API 暴露保存/恢复方法;
|
||||||
|
- 保存当前 HAL net、loaded component、function、source reports、motion matrix;
|
||||||
|
- 保存后修改 HAL pin 与 loadusr state;
|
||||||
|
- 恢复后 `axis.x.pos-cmd` 回到保存值;
|
||||||
|
- 恢复后临时 `post-save-component` 不再存在;
|
||||||
|
- 恢复后 `motion-controller` function 仍存在;
|
||||||
|
- DOM/body dataset 显示 saved/restored 状态。
|
||||||
|
|
||||||
|
关键验证:
|
||||||
|
|
||||||
|
```text
|
||||||
|
opfs_file_service_node_smoke=ok
|
||||||
|
sdk_surface_node_smoke=ok
|
||||||
|
project_release_gate_manifest_node_smoke=ok
|
||||||
|
project_release_artifact_url_workflow_node_smoke=ok
|
||||||
|
project_batch_acceptance_workflow_node_smoke=ok
|
||||||
|
project_batch_acceptance_artifact_node_smoke=ok
|
||||||
|
browser_real_simulation_page_smoke=ok
|
||||||
|
git diff --check ok
|
||||||
|
```
|
||||||
|
|
||||||
|
语义边界:
|
||||||
|
|
||||||
|
- 本批没有新增 G-code/interpreter/planner/kinematics 语义;
|
||||||
|
- session snapshot 只保存和恢复 Web virtual HAL runtime state、source reports 和
|
||||||
|
diagnostics evidence;
|
||||||
|
- virtual HAL 仍不声称提供 Linux kernel hard-realtime ABI、外部硬件驱动 ABI 或 native
|
||||||
|
HAL module ABI;
|
||||||
|
- Web 仿真 HAL 的满足性现在可在 real simulation page 中由用户显式保存、恢复并继续验证。
|
||||||
|
|
||||||
|
下一步工作内容:
|
||||||
|
|
||||||
|
1. 将 browser diagnostics artifact 的 URL workflow 接入 ini-panel workflow overview 页面,
|
||||||
|
让 release readiness 页面能显示 browser diagnostics validation rows。
|
||||||
|
2. 扩展 motion matrix 与 LinuxCNC sim configs 的关联,把 external-offsets、QtDragon
|
||||||
|
multi-joint、vismach remap sims 的 motion source evidence 显式映射到 matrix fixture rows。
|
||||||
|
3. 将 real simulation page 的 virtual HAL session snapshot 与 release diagnostics artifact
|
||||||
|
做联动,使保存的 Web CNC 仿真会话可直接输出 release-ready diagnostics evidence。
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
cloneVirtualHalState,
|
cloneVirtualHalState,
|
||||||
createVirtualHalCommandScriptFixtureReport,
|
createVirtualHalCommandScriptFixtureReport,
|
||||||
|
createVirtualHalMotionControllerMatrixReport,
|
||||||
createVirtualHalSimConfigSourceCoverageReport,
|
createVirtualHalSimConfigSourceCoverageReport,
|
||||||
createVirtualHalSimulationReplacementReport,
|
createVirtualHalSimulationReplacementReport,
|
||||||
createVirtualHalSourceComplianceReport,
|
createVirtualHalSourceComplianceReport,
|
||||||
@@ -66,6 +67,7 @@ function validateVirtualHalSessionPayload(virtualHal) {
|
|||||||
assertPlainObject(virtualHal.sourceCompliance, "virtual HAL session source compliance");
|
assertPlainObject(virtualHal.sourceCompliance, "virtual HAL session source compliance");
|
||||||
assertPlainObject(virtualHal.simConfigSourceCoverage, "virtual HAL session sim-config source coverage");
|
assertPlainObject(virtualHal.simConfigSourceCoverage, "virtual HAL session sim-config source coverage");
|
||||||
assertPlainObject(virtualHal.commandScriptFixtures, "virtual HAL session command script fixtures");
|
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") {
|
if (virtualHal.state.source !== "browser-virtual-hal") {
|
||||||
throw new Error(`Unsupported virtual HAL state source: ${virtualHal.state.source}`);
|
throw new Error(`Unsupported virtual HAL state source: ${virtualHal.state.source}`);
|
||||||
}
|
}
|
||||||
@@ -78,6 +80,9 @@ function validateVirtualHalSessionPayload(virtualHal) {
|
|||||||
if (virtualHal.commandScriptFixtures.complete !== true) {
|
if (virtualHal.commandScriptFixtures.complete !== true) {
|
||||||
throw new Error("virtual HAL command script fixtures are incomplete.");
|
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;
|
return virtualHal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +92,7 @@ export function createVirtualHalSessionPayload(halState = createVirtualHalState(
|
|||||||
const sourceCompliance = options.sourceCompliance ?? createVirtualHalSourceComplianceReport({ halState: state });
|
const sourceCompliance = options.sourceCompliance ?? createVirtualHalSourceComplianceReport({ halState: state });
|
||||||
const simConfigSourceCoverage = options.simConfigSourceCoverage ?? createVirtualHalSimConfigSourceCoverageReport(options);
|
const simConfigSourceCoverage = options.simConfigSourceCoverage ?? createVirtualHalSimConfigSourceCoverageReport(options);
|
||||||
const commandScriptFixtures = options.commandScriptFixtures ?? createVirtualHalCommandScriptFixtureReport({ ...options, halState: state });
|
const commandScriptFixtures = options.commandScriptFixtures ?? createVirtualHalCommandScriptFixtureReport({ ...options, halState: state });
|
||||||
|
const motionControllerMatrix = options.motionControllerMatrix ?? createVirtualHalMotionControllerMatrixReport(options);
|
||||||
return validateVirtualHalSessionPayload({
|
return validateVirtualHalSessionPayload({
|
||||||
apiName: "linuxcnc-wasm-virtual-hal-session-payload",
|
apiName: "linuxcnc-wasm-virtual-hal-session-payload",
|
||||||
payloadVersion: 1,
|
payloadVersion: 1,
|
||||||
@@ -96,6 +102,7 @@ export function createVirtualHalSessionPayload(halState = createVirtualHalState(
|
|||||||
sourceCompliance,
|
sourceCompliance,
|
||||||
simConfigSourceCoverage,
|
simConfigSourceCoverage,
|
||||||
commandScriptFixtures,
|
commandScriptFixtures,
|
||||||
|
motionControllerMatrix,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -486,6 +486,10 @@ private module paths:
|
|||||||
`saveSessionSnapshot()`, `saveMachineSessionSnapshot()`,
|
`saveSessionSnapshot()`, `saveMachineSessionSnapshot()`,
|
||||||
`loadMachineSessionSnapshot()`, and `validateSessionSnapshot()` for session
|
`loadMachineSessionSnapshot()`, and `validateSessionSnapshot()` for session
|
||||||
snapshot persistence.
|
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
|
- `machineFilePaths()`, `saveMachineTextFiles()`, `loadMachineTextFiles()`, and
|
||||||
`gcodeFilenameFromProgramPath()` for machine-file and G-code file helpers.
|
`gcodeFilenameFromProgramPath()` for machine-file and G-code file helpers.
|
||||||
- `readMachineSessionReadiness()` for checking persisted INI, parameter,
|
- `readMachineSessionReadiness()` for checking persisted INI, parameter,
|
||||||
|
|||||||
@@ -1474,6 +1474,8 @@
|
|||||||
<div class="machine-session-actions">
|
<div class="machine-session-actions">
|
||||||
<button type="button" data-check-machine-session>Check Session</button>
|
<button type="button" data-check-machine-session>Check Session</button>
|
||||||
<button type="button" data-load-machine-session>Load 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>
|
<label><input type="checkbox" checked data-use-loaded-session> Use Session</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="machine-session-status" data-machine-session-status>
|
<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 INI: <span data-machine-session-loaded-path="ini">-</span></span>
|
||||||
<span>Loaded parameters: <span data-machine-session-loaded-path="parameters">-</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>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>
|
</div>
|
||||||
<div class="active-codes" data-axis-shell="active-codes">
|
<div class="active-codes" data-axis-shell="active-codes">
|
||||||
@@ -1781,6 +1784,13 @@ M2
|
|||||||
import {
|
import {
|
||||||
loadMachineSessionFromOpfs,
|
loadMachineSessionFromOpfs,
|
||||||
} from "../../opfs/linuxcnc-machine-session-bridge.js";
|
} from "../../opfs/linuxcnc-machine-session-bridge.js";
|
||||||
|
import {
|
||||||
|
createMachineSessionSnapshotPayload,
|
||||||
|
createVirtualHalSessionPayload,
|
||||||
|
loadSessionSnapshot,
|
||||||
|
restoreVirtualHalStateFromSessionSnapshot,
|
||||||
|
saveSessionSnapshot,
|
||||||
|
} from "../../opfs/snapshot-store.js";
|
||||||
import {
|
import {
|
||||||
readMachineSessionReadiness,
|
readMachineSessionReadiness,
|
||||||
} from "../../opfs/machine-session-readiness.js";
|
} from "../../opfs/machine-session-readiness.js";
|
||||||
@@ -1842,6 +1852,8 @@ M2
|
|||||||
const machineSessionGcodeInput = document.querySelector("[data-machine-session-gcode]");
|
const machineSessionGcodeInput = document.querySelector("[data-machine-session-gcode]");
|
||||||
const checkMachineSessionButton = document.querySelector("[data-check-machine-session]");
|
const checkMachineSessionButton = document.querySelector("[data-check-machine-session]");
|
||||||
const loadMachineSessionButton = document.querySelector("[data-load-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 useLoadedSessionInput = document.querySelector("[data-use-loaded-session]");
|
||||||
const resetButton = document.querySelector("[data-playback-reset]");
|
const resetButton = document.querySelector("[data-playback-reset]");
|
||||||
const prevButton = document.querySelector("[data-playback-prev]");
|
const prevButton = document.querySelector("[data-playback-prev]");
|
||||||
@@ -1900,6 +1912,16 @@ M2
|
|||||||
opfsPath: "linuxcnc/gcode/axis-editor.ngc",
|
opfsPath: "linuxcnc/gcode/axis-editor.ngc",
|
||||||
status: "OPFS program not saved",
|
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 = {
|
let machineReadinessState = {
|
||||||
machineId: "axis-browser-sim",
|
machineId: "axis-browser-sim",
|
||||||
ready: false,
|
ready: false,
|
||||||
@@ -2644,6 +2666,17 @@ M2
|
|||||||
return { ...opfsProgramState };
|
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) {
|
function renderMachineReadiness(readiness) {
|
||||||
machineReadinessState = readiness;
|
machineReadinessState = readiness;
|
||||||
document.body.dataset.machineSessionReady = readiness.ready ? "true" : "false";
|
document.body.dataset.machineSessionReady = readiness.ready ? "true" : "false";
|
||||||
@@ -3489,6 +3522,69 @@ M2
|
|||||||
return state;
|
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 = {}) {
|
async function checkMachineSessionReadiness(options = {}) {
|
||||||
const machineId = String(options.machineId ?? machineSessionIdInput?.value ?? "axis-browser-sim").trim() || "axis-browser-sim";
|
const machineId = String(options.machineId ?? machineSessionIdInput?.value ?? "axis-browser-sim").trim() || "axis-browser-sim";
|
||||||
const sessionId = String(options.sessionId ?? machineSessionSnapshotInput?.value ?? "").trim();
|
const sessionId = String(options.sessionId ?? machineSessionSnapshotInput?.value ?? "").trim();
|
||||||
@@ -3876,6 +3972,9 @@ M2
|
|||||||
checkMachineSessionReadiness,
|
checkMachineSessionReadiness,
|
||||||
getMachineSessionLoadState: () => machineSessionLoadState,
|
getMachineSessionLoadState: () => machineSessionLoadState,
|
||||||
loadReadyMachineSession,
|
loadReadyMachineSession,
|
||||||
|
getVirtualHalSessionState: () => ({ ...virtualHalSessionState }),
|
||||||
|
saveVirtualHalSessionSnapshot,
|
||||||
|
restoreVirtualHalSessionSnapshot,
|
||||||
getRunMode: () => syncRunMode(),
|
getRunMode: () => syncRunMode(),
|
||||||
getRunControlState: () => renderRunControlState(),
|
getRunControlState: () => renderRunControlState(),
|
||||||
getAxisStatusbarState: () => renderAxisStatusbar(),
|
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", () => {
|
useLoadedSessionInput?.addEventListener("change", () => {
|
||||||
setUseLoadedSession(useLoadedSessionInput.checked);
|
setUseLoadedSession(useLoadedSessionInput.checked);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -252,6 +252,9 @@
|
|||||||
if (!api?.getMachineSessionLoadState || !api?.loadReadyMachineSession) {
|
if (!api?.getMachineSessionLoadState || !api?.loadReadyMachineSession) {
|
||||||
throw new Error("simulation API missing AXIS machine/session load controls");
|
throw new Error("simulation API missing AXIS machine/session load controls");
|
||||||
}
|
}
|
||||||
|
if (!api?.getVirtualHalSessionState || !api?.saveVirtualHalSessionSnapshot || !api?.restoreVirtualHalSessionSnapshot) {
|
||||||
|
throw new Error("simulation API missing virtual HAL session persistence controls");
|
||||||
|
}
|
||||||
if (!api?.getRunMode || !api?.getRunControlState || !api?.setUseLoadedSession || !api?.getRunSummary) {
|
if (!api?.getRunMode || !api?.getRunControlState || !api?.setUseLoadedSession || !api?.getRunSummary) {
|
||||||
throw new Error("simulation API missing AXIS run-mode controls");
|
throw new Error("simulation API missing AXIS run-mode controls");
|
||||||
}
|
}
|
||||||
@@ -317,7 +320,14 @@
|
|||||||
if (!doc.querySelector("[data-opfs-program-filename]") || !doc.querySelector("[data-save-program-opfs]") || !doc.querySelector("[data-load-program-opfs]")) {
|
if (!doc.querySelector("[data-opfs-program-filename]") || !doc.querySelector("[data-save-program-opfs]") || !doc.querySelector("[data-load-program-opfs]")) {
|
||||||
throw new Error("simulation page missing OPFS program persistence controls");
|
throw new Error("simulation page missing OPFS program persistence controls");
|
||||||
}
|
}
|
||||||
if (!doc.querySelector('[data-axis-shell="machine-session"]') || !doc.querySelector("[data-check-machine-session]") || !doc.querySelector("[data-load-machine-session]") || !doc.querySelector("[data-use-loaded-session]")) {
|
if (
|
||||||
|
!doc.querySelector('[data-axis-shell="machine-session"]') ||
|
||||||
|
!doc.querySelector("[data-check-machine-session]") ||
|
||||||
|
!doc.querySelector("[data-load-machine-session]") ||
|
||||||
|
!doc.querySelector("[data-save-virtual-hal-session]") ||
|
||||||
|
!doc.querySelector("[data-restore-virtual-hal-session]") ||
|
||||||
|
!doc.querySelector("[data-use-loaded-session]")
|
||||||
|
) {
|
||||||
throw new Error("simulation page missing machine/session readiness controls");
|
throw new Error("simulation page missing machine/session readiness controls");
|
||||||
}
|
}
|
||||||
if (!doc.querySelector("[data-axis-machine-control-state]")?.textContent.includes("browser virtual HAL active")) {
|
if (!doc.querySelector("[data-axis-machine-control-state]")?.textContent.includes("browser virtual HAL active")) {
|
||||||
@@ -726,6 +736,41 @@
|
|||||||
) {
|
) {
|
||||||
throw new Error(`AXIS-style virtual halcmd did not execute: ${JSON.stringify(virtualHalcmdResult)}`);
|
throw new Error(`AXIS-style virtual halcmd did not execute: ${JSON.stringify(virtualHalcmdResult)}`);
|
||||||
}
|
}
|
||||||
|
const savedVirtualHalSession = await api.saveVirtualHalSessionSnapshot({
|
||||||
|
sessionId: "axis-browser-virtual-hal-session",
|
||||||
|
machineId: "axis-browser-virtual-hal",
|
||||||
|
filename: "virtual-hal-session.json",
|
||||||
|
gcodeFilename: "axis-opfs-roundtrip.ngc",
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
savedVirtualHalSession.phase !== "saved" ||
|
||||||
|
savedVirtualHalSession.revision < virtualHalcmdResult.state.revision ||
|
||||||
|
!savedVirtualHalSession.snapshot?.payload?.virtualHal?.sourceCompliance?.complete ||
|
||||||
|
!savedVirtualHalSession.snapshot?.payload?.virtualHal?.motionControllerMatrix?.complete ||
|
||||||
|
doc.body.dataset.virtualHalSessionPhase !== "saved" ||
|
||||||
|
!doc.querySelector("[data-virtual-hal-session-status]")?.textContent.includes("Saved HAL session")
|
||||||
|
) {
|
||||||
|
throw new Error(`AXIS-style virtual HAL session save failed: ${JSON.stringify(savedVirtualHalSession)}`);
|
||||||
|
}
|
||||||
|
api.executeVirtualHalcmd("setp axis.x.pos-cmd 9.5\nloadusr post-save-component");
|
||||||
|
if (api.readVirtualHalPin("axis.x.pos-cmd")?.value !== 9.5) {
|
||||||
|
throw new Error("AXIS-style virtual HAL pre-restore mutation did not apply");
|
||||||
|
}
|
||||||
|
const restoredVirtualHalSession = await api.restoreVirtualHalSessionSnapshot({
|
||||||
|
sessionId: "axis-browser-virtual-hal-session",
|
||||||
|
filename: "virtual-hal-session.json",
|
||||||
|
});
|
||||||
|
if (
|
||||||
|
restoredVirtualHalSession.phase !== "restored" ||
|
||||||
|
restoredVirtualHalSession.restored !== true ||
|
||||||
|
api.readVirtualHalPin("axis.x.pos-cmd")?.value !== 1.75 ||
|
||||||
|
api.getVirtualHalState().hal.loadedComponents.some(({ component }) => component === "post-save-component") ||
|
||||||
|
!api.getVirtualHalState().hal.functions.some(({ name }) => name === "motion-controller") ||
|
||||||
|
doc.body.dataset.virtualHalSessionPhase !== "restored" ||
|
||||||
|
!doc.querySelector("[data-virtual-hal-session-status]")?.textContent.includes("Restored HAL session")
|
||||||
|
) {
|
||||||
|
throw new Error(`AXIS-style virtual HAL session restore failed: ${JSON.stringify(restoredVirtualHalSession)}`);
|
||||||
|
}
|
||||||
const virtualHalCommandScriptFixtures = api.getVirtualHalCommandScriptFixtureReport();
|
const virtualHalCommandScriptFixtures = api.getVirtualHalCommandScriptFixtureReport();
|
||||||
if (
|
if (
|
||||||
virtualHalCommandScriptFixtures.apiName !== "linuxcnc-wasm-virtual-hal-command-script-fixture-report" ||
|
virtualHalCommandScriptFixtures.apiName !== "linuxcnc-wasm-virtual-hal-command-script-fixture-report" ||
|
||||||
|
|||||||
@@ -266,12 +266,14 @@ assert.equal(virtualHalSessionPayload.state.hal.nets["x-pos"].includes("axis.x.p
|
|||||||
assert.equal(virtualHalSessionPayload.sourceCompliance.complete, true);
|
assert.equal(virtualHalSessionPayload.sourceCompliance.complete, true);
|
||||||
assert.equal(virtualHalSessionPayload.simConfigSourceCoverage.complete, true);
|
assert.equal(virtualHalSessionPayload.simConfigSourceCoverage.complete, true);
|
||||||
assert.equal(virtualHalSessionPayload.commandScriptFixtures.complete, true);
|
assert.equal(virtualHalSessionPayload.commandScriptFixtures.complete, true);
|
||||||
|
assert.equal(virtualHalSessionPayload.motionControllerMatrix.complete, true);
|
||||||
const virtualHalSnapshotPayload = createMachineSessionSnapshotPayload("virtual-hal-machine", {
|
const virtualHalSnapshotPayload = createMachineSessionSnapshotPayload("virtual-hal-machine", {
|
||||||
gcodeFilename: "virtual-hal.ngc",
|
gcodeFilename: "virtual-hal.ngc",
|
||||||
virtualHal: virtualHalSessionPayload,
|
virtualHal: virtualHalSessionPayload,
|
||||||
});
|
});
|
||||||
assert.equal(virtualHalSnapshotPayload.virtualHal.state.hal.signals["x-pos"].value, 2.25);
|
assert.equal(virtualHalSnapshotPayload.virtualHal.state.hal.signals["x-pos"].value, 2.25);
|
||||||
assert.equal(virtualHalSnapshotPayload.virtualHal.commandScriptFixtures.coveredActions.includes("loadusr"), true);
|
assert.equal(virtualHalSnapshotPayload.virtualHal.commandScriptFixtures.coveredActions.includes("loadusr"), true);
|
||||||
|
assert.equal(virtualHalSnapshotPayload.virtualHal.motionControllerMatrix.sourceFiles.includes("linuxcnc/src/emc/motion/axis.c"), true);
|
||||||
assert.deepEqual(
|
assert.deepEqual(
|
||||||
createMachineSessionSnapshotPayload("custom-machine", {
|
createMachineSessionSnapshotPayload("custom-machine", {
|
||||||
parameterFilename: "custom.var",
|
parameterFilename: "custom.var",
|
||||||
|
|||||||
@@ -747,6 +747,7 @@ assert.equal(virtualHalSessionPayload.state.hal.signals["x-pos"].value, 1.25);
|
|||||||
assert.equal(virtualHalSessionPayload.sourceCompliance.complete, true);
|
assert.equal(virtualHalSessionPayload.sourceCompliance.complete, true);
|
||||||
assert.equal(virtualHalSessionPayload.simConfigSourceCoverage.complete, true);
|
assert.equal(virtualHalSessionPayload.simConfigSourceCoverage.complete, true);
|
||||||
assert.equal(virtualHalSessionPayload.commandScriptFixtures.complete, true);
|
assert.equal(virtualHalSessionPayload.commandScriptFixtures.complete, true);
|
||||||
|
assert.equal(virtualHalSessionPayload.motionControllerMatrix.complete, true);
|
||||||
const virtualHalSessionSnapshot = createSessionSnapshot(
|
const virtualHalSessionSnapshot = createSessionSnapshot(
|
||||||
"sdk-virtual-hal-session",
|
"sdk-virtual-hal-session",
|
||||||
createMachineSessionSnapshotPayload("sdk-mill", {
|
createMachineSessionSnapshotPayload("sdk-mill", {
|
||||||
|
|||||||
Reference in New Issue
Block a user