完善虚拟HAL仿真替代能力

This commit is contained in:
2026-06-17 19:45:51 +08:00
parent 656cddf73a
commit a4d449ad48
17 changed files with 5005 additions and 302 deletions

View File

@@ -32,8 +32,21 @@ import {
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel,
VIRTUAL_HAL_COVERAGE_STATES,
VIRTUAL_HAL_PROJECT_PIN_GROUPS,
VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS,
VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES,
VIRTUAL_HAL_SOURCE_FILES,
VIRTUAL_HAL_SYSTEM_PIN_FAMILIES,
VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS,
applyVirtualHalAction,
applyVirtualHalPinUpdates,
applyVirtualHalToInterpSdk,
createVirtualHalSimulationRuntimeReport,
createLinuxCncIniSdk,
createLinuxCncInterpSdk,
createLinuxCncVirtualHalRuntime,
createVirtualHalIntegrityReport,
createMachineSessionSnapshotPayload,
createMachineSessionPersistenceDisplayViewModel,
createMachineSessionPersistenceRenderState,
@@ -63,6 +76,21 @@ import {
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
createProjectReleaseReadinessReport,
createProjectReleaseReadinessSummaryViewModel,
createVirtualHalBridgeActionPlan,
createVirtualHalBridgeReadiness,
createVirtualHalPinInventory,
createVirtualHalPinRegistry,
createVirtualHalProjectReport,
createVirtualHalSimulationReplacementReport,
createVirtualHalState,
createVirtualHalSystemCoverageReport,
createVirtualHalWasmBridgeSnapshot,
executeVirtualHalCommand,
executeVirtualHalcmd,
readVirtualHalPin,
stepVirtualHalMotionController,
stepVirtualHalMotion,
writeVirtualHalPin,
parseProjectReleaseReadinessArtifactJson,
createSessionSnapshot,
defaultMachinePaths,
@@ -186,6 +214,27 @@ const requiredExports = [
],
["createProjectReleaseReadinessReport", createProjectReleaseReadinessReport],
["createProjectReleaseReadinessSummaryViewModel", createProjectReleaseReadinessSummaryViewModel],
["applyVirtualHalAction", applyVirtualHalAction],
["applyVirtualHalPinUpdates", applyVirtualHalPinUpdates],
["applyVirtualHalToInterpSdk", applyVirtualHalToInterpSdk],
["createLinuxCncVirtualHalRuntime", createLinuxCncVirtualHalRuntime],
["createVirtualHalBridgeActionPlan", createVirtualHalBridgeActionPlan],
["createVirtualHalBridgeReadiness", createVirtualHalBridgeReadiness],
["createVirtualHalIntegrityReport", createVirtualHalIntegrityReport],
["createVirtualHalPinInventory", createVirtualHalPinInventory],
["createVirtualHalPinRegistry", createVirtualHalPinRegistry],
["createVirtualHalProjectReport", createVirtualHalProjectReport],
["createVirtualHalSimulationReplacementReport", createVirtualHalSimulationReplacementReport],
["createVirtualHalSimulationRuntimeReport", createVirtualHalSimulationRuntimeReport],
["createVirtualHalState", createVirtualHalState],
["createVirtualHalSystemCoverageReport", createVirtualHalSystemCoverageReport],
["createVirtualHalWasmBridgeSnapshot", createVirtualHalWasmBridgeSnapshot],
["executeVirtualHalCommand", executeVirtualHalCommand],
["executeVirtualHalcmd", executeVirtualHalcmd],
["readVirtualHalPin", readVirtualHalPin],
["stepVirtualHalMotionController", stepVirtualHalMotionController],
["stepVirtualHalMotion", stepVirtualHalMotion],
["writeVirtualHalPin", writeVirtualHalPin],
["parseProjectReleaseReadinessArtifactJson", parseProjectReleaseReadinessArtifactJson],
[
"validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson",
@@ -411,6 +460,202 @@ for (const [name, value] of requiredExports) {
assert.match(sdkReadmeText, new RegExp(`\\b${name}\\b`));
}
assert.equal(VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS.includes("lcinterp_hal_set_value"), true);
assert.equal(VIRTUAL_HAL_PROJECT_PIN_GROUPS.some(({ id }) => id === "axisui"), true);
assert.equal(VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES.includes("halcmd-setp-sets-net-show-getp-gets"), true);
assert.equal(VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES.includes("realtime-hal-simulation-replacement"), true);
assert.equal(VIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES.includes("motion-controller-simulation-replacement"), true);
assert.equal(VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.includes("linuxcnc-realtime-hal"), true);
assert.equal(VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.includes("halcmd"), true);
assert.equal(VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS.includes("motion-controller"), true);
assert.equal(VIRTUAL_HAL_COVERAGE_STATES.runtimeBoundary, "runtime-boundary");
assert.equal(VIRTUAL_HAL_SOURCE_FILES.halui, "linuxcnc/src/emc/usr_intf/halui.cc");
assert.equal(VIRTUAL_HAL_SYSTEM_PIN_FAMILIES.some(({ id }) => id === "iocontrol"), true);
assert.equal(VIRTUAL_HAL_SYSTEM_PIN_FAMILIES.some(({ id }) => id === "motion-core"), true);
assert.match(sdkIndexText, /\bVIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS\b/);
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS\b/);
assert.match(sdkIndexText, /\bVIRTUAL_HAL_PROJECT_PIN_GROUPS\b/);
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_PROJECT_PIN_GROUPS\b/);
assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES\b/);
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIMULATION_RUNTIME_CAPABILITIES\b/);
assert.match(sdkIndexText, /\bVIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS\b/);
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS\b/);
assert.match(sdkIndexText, /\bVIRTUAL_HAL_SYSTEM_PIN_FAMILIES\b/);
assert.match(sdkReadmeText, /\bVIRTUAL_HAL_SYSTEM_PIN_FAMILIES\b/);
assert.match(sdkIndexText, /\bexecuteVirtualHalCommand\b/);
assert.match(sdkReadmeText, /\bexecuteVirtualHalCommand\b/);
assert.match(sdkIndexText, /\bexecuteVirtualHalcmd\b/);
assert.match(sdkReadmeText, /\bexecuteVirtualHalcmd\b/);
assert.match(sdkIndexText, /\bstepVirtualHalMotion\b/);
assert.match(sdkReadmeText, /\bstepVirtualHalMotion\b/);
assert.match(sdkIndexText, /\bstepVirtualHalMotionController\b/);
assert.match(sdkReadmeText, /\bstepVirtualHalMotionController\b/);
assert.match(sdkIndexText, /\bcreateVirtualHalSimulationRuntimeReport\b/);
assert.match(sdkReadmeText, /\bcreateVirtualHalSimulationRuntimeReport\b/);
assert.match(sdkIndexText, /\bcreateVirtualHalSimulationReplacementReport\b/);
assert.match(sdkReadmeText, /\bcreateVirtualHalSimulationReplacementReport\b/);
assert.match(sdkIndexText, /\bcreateVirtualHalSystemCoverageReport\b/);
assert.match(sdkReadmeText, /\bcreateVirtualHalSystemCoverageReport\b/);
assert.match(sdkIndexText, /\bcreateVirtualHalPinRegistry\b/);
assert.match(sdkReadmeText, /\bcreateVirtualHalPinRegistry\b/);
assert.match(sdkIndexText, /\bcreateVirtualHalIntegrityReport\b/);
assert.match(sdkReadmeText, /\bcreateVirtualHalIntegrityReport\b/);
assert.match(sdkIndexText, /\breadVirtualHalPin\b/);
assert.match(sdkReadmeText, /\breadVirtualHalPin\b/);
assert.match(sdkIndexText, /\bwriteVirtualHalPin\b/);
assert.match(sdkReadmeText, /\bwriteVirtualHalPin\b/);
let sdkVirtualHal = createVirtualHalState();
sdkVirtualHal = applyVirtualHalAction(sdkVirtualHal, "estop");
sdkVirtualHal = applyVirtualHalAction(sdkVirtualHal, "power");
sdkVirtualHal = applyVirtualHalAction(sdkVirtualHal, "jog", { axis: "x", increment: "0.01 in", direction: 1 });
sdkVirtualHal = writeVirtualHalPin(sdkVirtualHal, { name: "spindle.0.speed-out", type: "HAL_FLOAT", value: 1800 });
sdkVirtualHal = writeVirtualHalPin(sdkVirtualHal, { name: "motion.tooloffset.z", type: "HAL_FLOAT", value: 2.5 });
sdkVirtualHal = applyVirtualHalPinUpdates(sdkVirtualHal, [
{ name: "halui.tool.number", type: "HAL_U32", value: 7 },
{ name: "halui.feed-override.value", type: "HAL_FLOAT", value: 0.8 },
]);
assert.equal(readVirtualHalPin(sdkVirtualHal, "spindle.0.speed-out").value, 1800);
assert.equal(readVirtualHalPin(sdkVirtualHal, "motion.tooloffset.z").value, 2.5);
assert.equal(readVirtualHalPin(sdkVirtualHal, "halui.tool.number").value, 7);
assert.equal(sdkVirtualHal.overrides.feed, 80);
const virtualHalSnapshot = createVirtualHalWasmBridgeSnapshot(sdkVirtualHal);
assert.equal(virtualHalSnapshot.apiName, "linuxcnc-wasm-virtual-hal-bridge-snapshot");
assert.ok(virtualHalSnapshot.values.some(({ name }) => name === "axisui.jog.x"));
assert.ok(virtualHalSnapshot.values.some(({ name, value }) => name === "spindle.0.speed-out" && value === 1800));
assert.ok(virtualHalSnapshot.values.some(({ name, value }) => name === "motion.tooloffset.z" && value === 2.5));
const virtualHalRegistry = createVirtualHalPinRegistry(sdkVirtualHal);
assert.equal(virtualHalRegistry.apiName, "linuxcnc-wasm-virtual-hal-pin-registry");
assert.equal(virtualHalRegistry.byName["spindle.0.speed-out"].value, 1800);
assert.equal(virtualHalRegistry.byName["motion.tooloffset.z"].value, 2.5);
assert.equal(virtualHalRegistry.byName["halui.tool.number"].value, 7);
assert.equal(virtualHalRegistry.rows.some(({ name, writable }) => name === "halui.machine.on" && writable === true), true);
const virtualHalIntegrity = createVirtualHalIntegrityReport({ halState: sdkVirtualHal, registry: virtualHalRegistry });
assert.equal(virtualHalIntegrity.apiName, "linuxcnc-wasm-virtual-hal-integrity-report");
assert.equal(virtualHalIntegrity.complete, true);
assert.equal(virtualHalIntegrity.missingPins.length, 0);
const virtualHalInventory = createVirtualHalPinInventory(sdkVirtualHal);
assert.equal(virtualHalInventory.apiName, "linuxcnc-wasm-virtual-hal-pin-inventory");
assert.equal(virtualHalInventory.groups.some(({ id, pinCount }) => id === "axisui" && pinCount > 0), true);
assert.equal(virtualHalInventory.groups.some(({ id, pinCount }) => id === "axis-motion" && pinCount > 0), true);
assert.equal(virtualHalInventory.groups.some(({ id, pinCount }) => id === "tool" && pinCount > 0), true);
const virtualHalSystemCoverage = createVirtualHalSystemCoverageReport({ snapshot: virtualHalSnapshot });
assert.equal(virtualHalSystemCoverage.apiName, "linuxcnc-wasm-virtual-hal-system-coverage-report");
assert.equal(virtualHalSystemCoverage.complete, true);
assert.equal(virtualHalSystemCoverage.realtimeHalRuntime, false);
assert.equal(virtualHalSystemCoverage.sourceFiles.includes("linuxcnc/bin/axis"), true);
assert.equal(virtualHalSystemCoverage.sourceFiles.includes("linuxcnc/src/emc/usr_intf/halui.cc"), true);
assert.equal(virtualHalSystemCoverage.sourceFiles.includes("linuxcnc/src/emc/task/taskclass.cc"), true);
assert.equal(virtualHalSystemCoverage.sourceFiles.includes("linuxcnc/src/emc/motion/motion.c"), true);
assert.equal(virtualHalSystemCoverage.families.some(({ id, coverage }) => id === "motion-core" && coverage === "runtime-boundary"), true);
assert.equal(virtualHalSystemCoverage.boundaryRows.some(({ id }) => id === "halui-task"), true);
const virtualHalBlockedReadiness = createVirtualHalBridgeReadiness({ snapshot: virtualHalSnapshot });
assert.equal(virtualHalBlockedReadiness.ready, false);
assert.equal(virtualHalBlockedReadiness.missing.includes("wasm-function-evidence"), true);
const virtualHalReadyReadiness = createVirtualHalBridgeReadiness({
snapshot: virtualHalSnapshot,
availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS,
});
assert.equal(virtualHalReadyReadiness.ready, true);
assert.equal(createVirtualHalBridgeActionPlan(virtualHalReadyReadiness).nextCommand, null);
const virtualHalReport = createVirtualHalProjectReport({
halState: sdkVirtualHal,
availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS,
});
assert.equal(virtualHalReport.apiName, "linuxcnc-wasm-virtual-hal-project-report");
assert.equal(virtualHalReport.ready, true);
assert.equal(virtualHalReport.pinInventory.pinCount, virtualHalReport.wasmBridgeSnapshot.values.length);
assert.equal(virtualHalReport.systemCoverage.phase, "covered-with-runtime-boundaries");
assert.equal(virtualHalReport.systemCoverage.unclassifiedSnapshotPins.length, 0);
assert.equal(virtualHalReport.pinRegistry.byName["spindle.0.speed-out"].value, 1800);
assert.equal(virtualHalReport.integrity.complete, true);
const virtualHalRuntime = createLinuxCncVirtualHalRuntime(sdkVirtualHal);
assert.equal(virtualHalRuntime.getProjectReport({ availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS }).ready, true);
const virtualHalcmdResult = virtualHalRuntime.executeHalCommand(`
loadrt trivkins
addf motion-controller servo-thread
net x-pos axis.x.pos-cmd joint.0.pos-fb
setp axis.x.pos-cmd 2.5
sets x-pos 1.25
show pin axis.x.*
motion-step x=2.0
`);
assert.equal(virtualHalcmdResult.apiName, "linuxcnc-wasm-virtual-halcmd-result");
assert.equal(virtualHalcmdResult.ok, true);
assert.equal(virtualHalcmdResult.rows.some(({ action }) => action === "net"), true);
assert.equal(virtualHalRuntime.getState().hal.signals["x-pos"].value, 1.25);
assert.equal(virtualHalRuntime.getState().hal.nets["x-pos"].includes("axis.x.pos-cmd"), true);
assert.equal(virtualHalRuntime.getState().hal.loadedComponents.some(({ component }) => component === "trivkins"), true);
assert.equal(virtualHalRuntime.getState().hal.functions.some(({ name }) => name === "motion-controller"), true);
assert.ok(virtualHalRuntime.getState().position.x > 1.25);
const directHalcmdResult = executeVirtualHalCommand(virtualHalRuntime.getState(), "getp axis.x.pos-cmd\ngets x-pos");
assert.equal(directHalcmdResult.ok, true);
assert.match(directHalcmdResult.output, /axis\.x\.pos-cmd/);
assert.match(directHalcmdResult.output, /x-pos/);
const directHalcmdAliasResult = executeVirtualHalcmd(virtualHalRuntime.getState(), "setp halui.machine.on 1\nshow pin halui.machine.*");
assert.equal(directHalcmdAliasResult.ok, true);
assert.equal(directHalcmdAliasResult.apiName, "linuxcnc-wasm-virtual-halcmd-result");
assert.match(directHalcmdAliasResult.output, /halui\.machine\.is-on/);
const steppedVirtualHal = stepVirtualHalMotion(virtualHalRuntime.getState(), {
target: { x: 3 },
maxVelocity: 10,
dt: 0.1,
});
assert.ok(steppedVirtualHal.position.x > virtualHalRuntime.getState().position.x);
const motionControllerStep = stepVirtualHalMotionController(virtualHalRuntime.getState(), {
target: { x: 4 },
maxVelocity: 10,
dt: 0.1,
cycleCount: 3,
});
assert.equal(motionControllerStep.apiName, "linuxcnc-wasm-virtual-motion-controller-step");
assert.equal(motionControllerStep.ok, true);
assert.ok(motionControllerStep.cycleCount >= 1);
assert.ok(motionControllerStep.state.position.x > virtualHalRuntime.getState().position.x);
const simulationRuntimeReport = virtualHalRuntime.getSimulationRuntimeReport();
assert.equal(simulationRuntimeReport.apiName, "linuxcnc-wasm-virtual-hal-simulation-runtime-report");
assert.equal(simulationRuntimeReport.replacesHostRuntimeForSimulation, true);
assert.deepEqual(simulationRuntimeReport.replacementTargets, VIRTUAL_HAL_SIMULATION_REPLACEMENT_TARGETS);
assert.equal(simulationRuntimeReport.hardRealtime, false);
assert.deepEqual(simulationRuntimeReport.hostRequirements, []);
assert.equal(simulationRuntimeReport.capabilities.includes("servo-period-motion-step"), true);
assert.equal(createVirtualHalSimulationRuntimeReport(virtualHalRuntime.getState()).ready, true);
const runtimeMotionControllerStep = virtualHalRuntime.stepMotionController({
target: { x: 4.5 },
maxVelocity: 20,
dt: 0.1,
cycleCount: 2,
});
assert.equal(runtimeMotionControllerStep.apiName, "linuxcnc-wasm-virtual-motion-controller-step");
assert.ok(virtualHalRuntime.getState().position.x > motionControllerStep.state.position.x);
const replacementReport = createVirtualHalSimulationReplacementReport(virtualHalRuntime.getState());
assert.equal(replacementReport.apiName, "linuxcnc-wasm-virtual-hal-simulation-replacement-report");
assert.equal(replacementReport.ready, true);
assert.equal(replacementReport.replacesHostRuntimeForSimulation, true);
assert.equal(replacementReport.replacements["linuxcnc-realtime-hal"].ready, true);
assert.equal(replacementReport.replacements.halcmd.ready, true);
assert.equal(replacementReport.replacements["motion-controller"].ready, true);
assert.equal(replacementReport.hardRealtime, false);
assert.equal(virtualHalRuntime.getSimulationReplacementReport().ready, true);
const runtimeProjectReport = virtualHalRuntime.getProjectReport({ availableFunctions: VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS });
assert.equal(runtimeProjectReport.simulationRuntime.replacesHostRuntimeForSimulation, true);
assert.equal(runtimeProjectReport.systemCoverage.unclassifiedSnapshotPins.length, 0);
const fakeInterpApplyResult = applyVirtualHalToInterpSdk(
{
applyVirtualHalSnapshot(snapshot, options) {
return {
apiName: "fake-virtual-hal-apply-result",
applied: snapshot.values.length,
reset: options.reset !== false,
};
},
},
sdkVirtualHal,
{ reset: false },
);
assert.equal(fakeInterpApplyResult.apiName, "fake-virtual-hal-apply-result");
assert.equal(fakeInterpApplyResult.reset, false);
assert.equal(machineIniPath("sdk-mill"), "linuxcnc/machines/sdk-mill/machine.ini");
assert.deepEqual(defaultMachinePaths("sdk-mill"), machineFilePaths("sdk-mill"));
assert.equal(gcodeProgramPath("demo.ngc"), "linuxcnc/gcode/demo.ngc");