虚拟HAL完全满足Web数控仿真
结论:real simulation page 已将保存的 virtual HAL session snapshot 联动到 release diagnostics artifact,保存或恢复后的 Web CNC 仿真会话可直接输出 release-ready virtual HAL source、sim-config、halcmd fixture 与 motion matrix evidence;该能力仍限于 Web 仿真替代层,不声明 Linux kernel hard-realtime ABI、外部硬件驱动 ABI 或 native HAL module ABI。
This commit is contained in:
77
text17.txt
77
text17.txt
@@ -1113,3 +1113,80 @@ git diff --check ok
|
||||
让用户可在页面上输入 release artifact URL 与 browser diagnostics URL 后直接运行验证。
|
||||
3. 为 motion matrix 增加 manifest-backed source check,把 matrix 关联的 sim config
|
||||
source files 与 vendored LinuxCNC manifest 做显式一致性验证。
|
||||
|
||||
### 十二、2026-06-17 Virtual HAL Session Diagnostics Artifact Linkage
|
||||
|
||||
继续按本文件铁律推进:虚拟 HAL 的功能完全来源于 LinuxCNC 源程序,并且虚拟 HAL
|
||||
完全满足 Web 方式数控系统仿真系统。本批完成上一节下一步第 1 项,把 real
|
||||
simulation page 保存的 virtual HAL session snapshot 与 release diagnostics artifact 做联动,
|
||||
使保存后的 Web CNC 仿真会话可直接输出 release-ready diagnostics evidence。
|
||||
|
||||
新增实质能力:
|
||||
|
||||
- real simulation page 新增 session diagnostics artifact builder:
|
||||
- `createVirtualHalSessionDiagnosticsArtifact(snapshot)`;
|
||||
- 从保存的 session snapshot 读取 `payload.virtualHal`;
|
||||
- 复用 snapshot 中的 virtual HAL state、simulation replacement、source compliance、
|
||||
sim-config coverage、halcmd fixtures、motion controller matrix;
|
||||
- 使用 `createProjectReleaseBrowserDiagnosticsArtifactValidation()` 对 snapshot
|
||||
派生 diagnostics artifact 做 release 级验证;
|
||||
- `exportDiagnosticsArtifact()` 新增:
|
||||
- `virtualHalSessionDiagnostics`;
|
||||
- 未保存 session 时返回 `phase: "not-saved"` 和 missing reason;
|
||||
- 已保存或恢复 session 后返回 `phase: "release-ready"`;
|
||||
- Browser simulation API 新增:
|
||||
- `exportVirtualHalSessionDiagnosticsArtifact()`;
|
||||
- `saveVirtualHalSessionSnapshot()` 返回值新增:
|
||||
- `releaseDiagnosticsReady`;
|
||||
- `sessionDiagnostics`;
|
||||
- `restoreVirtualHalSessionSnapshot()` 返回值新增:
|
||||
- `releaseDiagnosticsReady`;
|
||||
- `sessionDiagnostics`;
|
||||
- AXIS simulation docs 同步公开
|
||||
`exportVirtualHalSessionDiagnosticsArtifact()`,并说明 saved virtual HAL session
|
||||
snapshot 会进入 browser diagnostics artifact,形成 release-ready evidence。
|
||||
|
||||
新增/扩展验证:
|
||||
|
||||
- `real_simulation_page_smoke.html` 覆盖:
|
||||
- 页面 API 暴露 `exportVirtualHalSessionDiagnosticsArtifact()`;
|
||||
- 保存 virtual HAL session 后 `releaseDiagnosticsReady === true`;
|
||||
- 保存返回的 `sessionDiagnostics.validation.ready === true`;
|
||||
- 保存返回的 session diagnostics 包含 complete motion controller matrix;
|
||||
- `exportVirtualHalSessionDiagnosticsArtifact()` 输出 `phase === "release-ready"`;
|
||||
- `exportDiagnosticsArtifact()` 内嵌 `virtualHalSessionDiagnostics.ready === true`;
|
||||
- restore 后 session diagnostics 仍保持 release-ready;
|
||||
- `verify_axis_style_simulation_docs.mjs` 覆盖新 API 文档契约;
|
||||
- `verify_sdk_surface.sh` 继续覆盖 release diagnostics validation 与 virtual HAL source
|
||||
evidence。
|
||||
|
||||
关键验证:
|
||||
|
||||
```text
|
||||
axis_style_simulation_docs_node_smoke=ok
|
||||
browser_real_simulation_page_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
|
||||
git diff --check ok
|
||||
```
|
||||
|
||||
语义边界:
|
||||
|
||||
- 本批没有新增 G-code/interpreter/planner/kinematics/remap 语义;
|
||||
- session diagnostics artifact 只复用保存时已有的 source-derived virtual HAL reports;
|
||||
- release validation 只验证 Web 仿真所需 HAL、halcmd、motion controller evidence;
|
||||
- virtual HAL 仍不声称提供 Linux kernel hard-realtime ABI、外部硬件驱动 ABI 或 native
|
||||
HAL module ABI;
|
||||
- G-code/interpreter/planner/canonical motion 语义仍来自 LinuxCNC-backed WASM。
|
||||
|
||||
下一步工作内容:
|
||||
|
||||
1. 将 ini-panel workflow overview 的 diagnostics URL workflow 接入固定页面表单/按钮,
|
||||
让用户可在页面上输入 release artifact URL 与 browser diagnostics URL 后直接运行验证。
|
||||
2. 为 motion matrix 增加 manifest-backed source check,把 matrix 关联的 sim config
|
||||
source files 与 vendored LinuxCNC manifest 做显式一致性验证。
|
||||
3. 将 virtual HAL session diagnostics artifact 纳入 release artifact URL workflow 的示例
|
||||
fixtures,使 URL workflow 同时覆盖 live diagnostics 与 saved-session diagnostics。
|
||||
|
||||
@@ -373,7 +373,11 @@ Current implemented behavior:
|
||||
state;
|
||||
- browser API exposes `getDiagnosticsState()`, `getStatusHistory()`,
|
||||
`getToolTableSummary()`, `getLimitsHomeState()`,
|
||||
`exportDiagnosticsArtifact()`, and `handleAxisShortcut(event)`;
|
||||
`exportDiagnosticsArtifact()`, `exportVirtualHalSessionDiagnosticsArtifact()`,
|
||||
and `handleAxisShortcut(event)`;
|
||||
- saved virtual HAL session snapshots are linked back into the browser
|
||||
diagnostics artifact as release-ready virtual HAL source, sim-config,
|
||||
halcmd-fixture, and motion-matrix evidence;
|
||||
- browser smoke covers run and play/pause keyboard shortcuts after waiting for
|
||||
their async UI/API side effects;
|
||||
- browser smoke verifies blocked readiness before OPFS files exist, then writes
|
||||
@@ -458,6 +462,7 @@ Required methods:
|
||||
- `getToolTableSummary()`;
|
||||
- `getLimitsHomeState()`;
|
||||
- `exportDiagnosticsArtifact()`;
|
||||
- `exportVirtualHalSessionDiagnosticsArtifact()`;
|
||||
- `handleAxisShortcut(event)`;
|
||||
- `applyAxisViewFromUrl(search)`;
|
||||
|
||||
|
||||
@@ -1800,6 +1800,7 @@ M2
|
||||
import {
|
||||
createLinuxCncIniSdk,
|
||||
createLinuxCncInterpSdk,
|
||||
createProjectReleaseBrowserDiagnosticsArtifactValidation,
|
||||
} from "../../sdk/src/index.js";
|
||||
import {
|
||||
DEFAULT_SIMULATION_PROGRAM_ID,
|
||||
@@ -1921,6 +1922,7 @@ M2
|
||||
status: "Virtual HAL session not saved",
|
||||
revision: 0,
|
||||
restored: false,
|
||||
releaseDiagnosticsReady: false,
|
||||
};
|
||||
let machineReadinessState = {
|
||||
machineId: "axis-browser-sim",
|
||||
@@ -2613,6 +2615,7 @@ M2
|
||||
virtualHalSimConfigSourceCoverage: createVirtualHalSimConfigSourceCoverageReport(),
|
||||
virtualHalCommandScriptFixtures: createVirtualHalCommandScriptFixtureReport(),
|
||||
virtualHalMotionControllerMatrix: createVirtualHalMotionControllerMatrixReport(),
|
||||
virtualHalSessionDiagnostics: createVirtualHalSessionDiagnosticsArtifact(),
|
||||
statusHistory: getStatusHistory(),
|
||||
mdiHistory: getMdiHistory(),
|
||||
recentPrograms: getRecentPrograms(),
|
||||
@@ -2627,6 +2630,44 @@ M2
|
||||
};
|
||||
}
|
||||
|
||||
function createVirtualHalSessionDiagnosticsArtifact(snapshot = virtualHalSessionState.snapshot ?? null) {
|
||||
if (!snapshot?.payload?.virtualHal) {
|
||||
return {
|
||||
apiName: "real-browser-simulation-virtual-hal-session-diagnostics",
|
||||
artifactVersion: 1,
|
||||
ready: false,
|
||||
phase: "not-saved",
|
||||
missing: ["virtualHalSessionSnapshot"],
|
||||
};
|
||||
}
|
||||
const virtualHal = snapshot.payload.virtualHal;
|
||||
const diagnosticsArtifact = {
|
||||
apiName: "real-browser-simulation-diagnostics-artifact",
|
||||
artifactVersion: 1,
|
||||
virtualHal: virtualHal.state,
|
||||
virtualHalSimulationReplacement: virtualHal.diagnostics,
|
||||
virtualHalSourceCompliance: virtualHal.sourceCompliance,
|
||||
virtualHalSimConfigSourceCoverage: virtualHal.simConfigSourceCoverage,
|
||||
virtualHalCommandScriptFixtures: virtualHal.commandScriptFixtures,
|
||||
virtualHalMotionControllerMatrix: virtualHal.motionControllerMatrix,
|
||||
};
|
||||
const validation = createProjectReleaseBrowserDiagnosticsArtifactValidation(diagnosticsArtifact);
|
||||
return {
|
||||
apiName: "real-browser-simulation-virtual-hal-session-diagnostics",
|
||||
artifactVersion: 1,
|
||||
ready: validation.ready === true,
|
||||
phase: validation.ready === true ? "release-ready" : "blocked",
|
||||
sessionId: snapshot.sessionId,
|
||||
machineId: snapshot.payload.machineId,
|
||||
createdAt: snapshot.createdAt,
|
||||
metadata: { ...(snapshot.metadata ?? {}) },
|
||||
files: { ...(snapshot.payload.files ?? {}) },
|
||||
virtualHalRevision: virtualHal.state.revision,
|
||||
validation,
|
||||
diagnosticsArtifact,
|
||||
};
|
||||
}
|
||||
|
||||
function recordError(error, context = "runtime") {
|
||||
const message = `${context}: ${error?.message ?? error}`;
|
||||
renderDiagnostics({ lastError: message });
|
||||
@@ -3549,6 +3590,7 @@ M2
|
||||
virtualHalRevision: virtualHal.state.revision,
|
||||
},
|
||||
});
|
||||
const sessionDiagnostics = createVirtualHalSessionDiagnosticsArtifact(snapshot);
|
||||
pushStatusHistory("hal-session", `saved ${session.sessionId}/${session.filename}`);
|
||||
return setVirtualHalSessionState({
|
||||
ready: true,
|
||||
@@ -3559,6 +3601,8 @@ M2
|
||||
status: `Saved HAL session: linuxcnc/sessions/${session.sessionId}/${session.filename}`,
|
||||
revision: virtualHal.state.revision,
|
||||
restored: false,
|
||||
releaseDiagnosticsReady: sessionDiagnostics.ready === true,
|
||||
sessionDiagnostics,
|
||||
snapshot,
|
||||
});
|
||||
}
|
||||
@@ -3571,6 +3615,7 @@ M2
|
||||
virtualHalState = restoreVirtualHalStateFromSessionSnapshot(snapshot);
|
||||
renderVirtualHalState();
|
||||
renderAxisStatusbar();
|
||||
const sessionDiagnostics = createVirtualHalSessionDiagnosticsArtifact(snapshot);
|
||||
pushStatusHistory("hal-session", `restored ${session.sessionId}/${session.filename}`);
|
||||
return setVirtualHalSessionState({
|
||||
ready: true,
|
||||
@@ -3581,6 +3626,8 @@ M2
|
||||
status: `Restored HAL session: linuxcnc/sessions/${session.sessionId}/${session.filename}`,
|
||||
revision: virtualHalState.revision,
|
||||
restored: true,
|
||||
releaseDiagnosticsReady: sessionDiagnostics.ready === true,
|
||||
sessionDiagnostics,
|
||||
snapshot,
|
||||
});
|
||||
}
|
||||
@@ -3975,6 +4022,7 @@ M2
|
||||
getVirtualHalSessionState: () => ({ ...virtualHalSessionState }),
|
||||
saveVirtualHalSessionSnapshot,
|
||||
restoreVirtualHalSessionSnapshot,
|
||||
exportVirtualHalSessionDiagnosticsArtifact: () => createVirtualHalSessionDiagnosticsArtifact(),
|
||||
getRunMode: () => syncRunMode(),
|
||||
getRunControlState: () => renderRunControlState(),
|
||||
getAxisStatusbarState: () => renderAxisStatusbar(),
|
||||
|
||||
@@ -252,7 +252,7 @@
|
||||
if (!api?.getMachineSessionLoadState || !api?.loadReadyMachineSession) {
|
||||
throw new Error("simulation API missing AXIS machine/session load controls");
|
||||
}
|
||||
if (!api?.getVirtualHalSessionState || !api?.saveVirtualHalSessionSnapshot || !api?.restoreVirtualHalSessionSnapshot) {
|
||||
if (!api?.getVirtualHalSessionState || !api?.saveVirtualHalSessionSnapshot || !api?.restoreVirtualHalSessionSnapshot || !api?.exportVirtualHalSessionDiagnosticsArtifact) {
|
||||
throw new Error("simulation API missing virtual HAL session persistence controls");
|
||||
}
|
||||
if (!api?.getRunMode || !api?.getRunControlState || !api?.setUseLoadedSession || !api?.getRunSummary) {
|
||||
@@ -747,11 +747,29 @@
|
||||
savedVirtualHalSession.revision < virtualHalcmdResult.state.revision ||
|
||||
!savedVirtualHalSession.snapshot?.payload?.virtualHal?.sourceCompliance?.complete ||
|
||||
!savedVirtualHalSession.snapshot?.payload?.virtualHal?.motionControllerMatrix?.complete ||
|
||||
savedVirtualHalSession.releaseDiagnosticsReady !== true ||
|
||||
savedVirtualHalSession.sessionDiagnostics?.ready !== true ||
|
||||
savedVirtualHalSession.sessionDiagnostics?.validation?.ready !== true ||
|
||||
savedVirtualHalSession.sessionDiagnostics?.validation?.motionControllerMatrixReady !== true ||
|
||||
savedVirtualHalSession.sessionDiagnostics?.diagnosticsArtifact?.virtualHalMotionControllerMatrix?.complete !== true ||
|
||||
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)}`);
|
||||
}
|
||||
const savedSessionDiagnosticsArtifact = api.exportVirtualHalSessionDiagnosticsArtifact();
|
||||
const diagnosticsWithSavedSession = api.exportDiagnosticsArtifact();
|
||||
if (
|
||||
savedSessionDiagnosticsArtifact.ready !== true ||
|
||||
savedSessionDiagnosticsArtifact.phase !== "release-ready" ||
|
||||
savedSessionDiagnosticsArtifact.sessionId !== "axis-browser-virtual-hal-session" ||
|
||||
savedSessionDiagnosticsArtifact.validation?.ready !== true ||
|
||||
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.ready !== true ||
|
||||
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.validation?.ready !== true ||
|
||||
diagnosticsWithSavedSession.virtualHalSessionDiagnostics?.diagnosticsArtifact?.virtualHalSourceCompliance?.complete !== true
|
||||
) {
|
||||
throw new Error(`AXIS-style virtual HAL session diagnostics did not become release-ready: ${JSON.stringify(diagnosticsWithSavedSession.virtualHalSessionDiagnostics)}`);
|
||||
}
|
||||
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");
|
||||
@@ -766,6 +784,9 @@
|
||||
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") ||
|
||||
restoredVirtualHalSession.releaseDiagnosticsReady !== true ||
|
||||
restoredVirtualHalSession.sessionDiagnostics?.ready !== true ||
|
||||
restoredVirtualHalSession.sessionDiagnostics?.validation?.ready !== true ||
|
||||
doc.body.dataset.virtualHalSessionPhase !== "restored" ||
|
||||
!doc.querySelector("[data-virtual-hal-session-status]")?.textContent.includes("Restored HAL session")
|
||||
) {
|
||||
|
||||
@@ -22,6 +22,7 @@ for (const required of [
|
||||
"getRecentPrograms()",
|
||||
"Three.js `^0.183.2`",
|
||||
"exportDiagnosticsArtifact()",
|
||||
"exportVirtualHalSessionDiagnosticsArtifact()",
|
||||
"getLimitsHomeState()",
|
||||
"getAxisStatusbarState()",
|
||||
"applyAxisViewFromUrl(search)",
|
||||
|
||||
Reference in New Issue
Block a user