完善虚拟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

@@ -21,6 +21,23 @@ import {
runRealBrowserSimulation,
zoomToolpathViewBox,
} from "../../../runtime/ui/simulation/simulation-app.js";
import {
VIRTUAL_HAL_AXISUI_PINS,
applyVirtualHalAction,
applyVirtualHalPinUpdates,
createLinuxCncInterpSdk,
createLinuxCncVirtualHalRuntime,
createVirtualHalDroState,
createVirtualHalIntegrityReport,
createVirtualHalLimitsHomeState,
createVirtualHalMachineStatusState,
createVirtualHalPinRegistry,
createVirtualHalState,
createVirtualHalWasmBridgeSnapshot,
parseVirtualHalJogIncrement,
readVirtualHalPin,
writeVirtualHalPin,
} from "../../../runtime/sdk/src/index.js";
import {
SIMULATION_TEST_PROGRAMS as directoryPrograms,
} from "../../../runtime/ui/simulation/programs/index.js";
@@ -56,6 +73,62 @@ const ids = new Set(programs.map(({ id }) => id));
assert.equal(ids.size, programs.length, "program ids should be unique");
assert.ok(ids.has("arc-g2-g3"), "program inventory should include an arc fixture");
assert.ok(ids.has("drill-g81"), "program inventory should include a canned-cycle fixture");
assert.equal(VIRTUAL_HAL_AXISUI_PINS.component, "axisui");
assert.ok(VIRTUAL_HAL_AXISUI_PINS.pins.some(({ name }) => name === "jog.x"), "virtual HAL should mirror AXIS axisui jog pins");
assert.ok(VIRTUAL_HAL_AXISUI_PINS.pins.some(({ name }) => name === "jog.increment"), "virtual HAL should expose AXIS jog increment pin");
assert.equal(parseVirtualHalJogIncrement("Continuous"), 0);
assert.equal(parseVirtualHalJogIncrement("0.01 in"), 0.01);
assert.equal(Number(parseVirtualHalJogIncrement("1 mm").toFixed(6)), 0.03937);
let virtualHal = createVirtualHalState();
assert.equal(virtualHal.task.state, "ESTOP");
virtualHal = applyVirtualHalAction(virtualHal, "estop");
assert.equal(virtualHal.task.state, "ESTOP_RESET");
virtualHal = applyVirtualHalAction(virtualHal, "power");
assert.equal(virtualHal.task.state, "ON");
virtualHal = applyVirtualHalAction(virtualHal, "home-all");
assert.equal(createVirtualHalLimitsHomeState(virtualHal).axes.x.homed, "yes");
virtualHal = applyVirtualHalAction(virtualHal, "jog", { axis: "x", increment: "0.01 in", direction: 1 });
assert.equal(virtualHal.pins["jog.x"], true);
assert.equal(virtualHal.pins["jog.increment"], 0.01);
assert.equal(createVirtualHalDroState(virtualHal).actual.x, "0.010");
const jogSnapshot = createVirtualHalWasmBridgeSnapshot(virtualHal);
assert.ok(jogSnapshot.values.some(({ name, type, value }) => name === "jog.x" && type === "HAL_BIT" && value === 1));
assert.ok(jogSnapshot.values.some(({ name, type, value }) => name === "axisui.jog.x" && type === "HAL_BIT" && value === 1));
assert.ok(jogSnapshot.values.some(({ name, type, value }) => name === "jog.increment" && type === "HAL_FLOAT" && value === 0.01));
assert.ok(jogSnapshot.values.some(({ name, type, value }) => name === "axisui.jog.increment" && type === "HAL_FLOAT" && value === 0.01));
virtualHal = applyVirtualHalAction(virtualHal, "spindle", { command: "cw", speed: 1200 });
virtualHal = applyVirtualHalAction(virtualHal, "coolant", { name: "flood", enabled: true });
virtualHal = writeVirtualHalPin(virtualHal, { name: "motion.tooloffset.z", type: "HAL_FLOAT", value: 1.25 });
virtualHal = applyVirtualHalPinUpdates(virtualHal, [
{ name: "halui.tool.number", type: "HAL_U32", value: 4 },
{ name: "halui.feed-override.value", type: "HAL_FLOAT", value: 0.9 },
]);
assert.equal(readVirtualHalPin(virtualHal, "motion.tooloffset.z").value, 1.25);
assert.equal(readVirtualHalPin(virtualHal, "halui.tool.number").value, 4);
assert.equal(virtualHal.overrides.feed, 90);
const virtualHalRegistry = createVirtualHalPinRegistry(virtualHal);
assert.equal(virtualHalRegistry.apiName, "linuxcnc-wasm-virtual-hal-pin-registry");
assert.equal(virtualHalRegistry.byName["halui.machine.on"].writable, true);
assert.equal(virtualHalRegistry.byName["motion.tooloffset.z"].value, 1.25);
const virtualHalIntegrity = createVirtualHalIntegrityReport({ halState: virtualHal, registry: virtualHalRegistry });
assert.equal(virtualHalIntegrity.complete, true);
const virtualMachineStatus = createVirtualHalMachineStatusState(virtualHal);
assert.equal(virtualMachineStatus.source, "browser-virtual-hal");
assert.equal(virtualMachineStatus.spindle.direction, "cw");
assert.equal(virtualMachineStatus.spindle.speed, "1200");
assert.equal(virtualMachineStatus.coolant.flood, "on");
const virtualHalRuntime = createLinuxCncVirtualHalRuntime(virtualHal);
virtualHalRuntime.writePin({ name: "spindle.0.speed-out", type: "HAL_FLOAT", value: 1400 });
assert.equal(virtualHalRuntime.readPin("spindle.0.speed-out").value, 1400);
assert.equal(virtualHalRuntime.getIntegrityReport().complete, true);
const runtimeSnapshot = virtualHalRuntime.getWasmBridgeSnapshot();
assert.equal(runtimeSnapshot.apiName, "linuxcnc-wasm-virtual-hal-bridge-snapshot");
assert.ok(runtimeSnapshot.values.some(({ name, type, value }) => name === "jog.x" && type === "HAL_BIT" && value === 1));
assert.ok(runtimeSnapshot.values.some(({ name, type, value }) => name === "axisui.jog.x" && type === "HAL_BIT" && value === 1));
assert.ok(runtimeSnapshot.values.some(({ name, type, value }) => name === "halui.machine.is-on" && type === "HAL_BIT" && value === 1));
assert.ok(runtimeSnapshot.values.some(({ name, type, value }) => name === "spindle.0.speed-out" && type === "HAL_FLOAT" && value === 1400));
assert.ok(runtimeSnapshot.values.some(({ name, type, value }) => name === "iocontrol.0.coolant-flood" && type === "HAL_BIT" && value === 1));
assert.throws(
() => getSimulationTestProgram("missing-program"),
@@ -91,8 +164,8 @@ assert.equal(arcModal.apiName, "real-browser-simulation-modal-state");
assert.equal(arcModal.source, "linuxcnc-update-tag");
assert.ok(arcModal.raw.includes("canon_event=UPDATE_TAG line=1"));
assert.ok(arcModal.rows.some(({ id, value, source }) => id === "plane" && value === "plane=170" && source === "linuxcnc-update-tag"));
assert.equal(unavailableMachineStatus.spindle.state, "n/a");
assert.equal(unavailableMachineStatus.coolant.mist, "n/a");
assert.equal(unavailableMachineStatus.spindle.state, "off");
assert.equal(unavailableMachineStatus.coolant.mist, "off");
const runtimeMachineStatus = createMachineStatusState([
"canon_event=SET_SPINDLE_SPEED spindle=0 speed=1200",
@@ -108,7 +181,7 @@ const runtimeMachineStatus = createMachineStatusState([
"canon_event=ENABLE_SPEED_OVERRIDE spindle=0",
].join("\n"));
assert.equal(runtimeMachineStatus.spindle.state, "off");
assert.equal(runtimeMachineStatus.spindle.direction, "cw");
assert.equal(runtimeMachineStatus.spindle.direction, "stopped");
assert.equal(runtimeMachineStatus.spindle.speed, "1200");
assert.equal(runtimeMachineStatus.coolant.mist, "off");
assert.equal(runtimeMachineStatus.coolant.flood, "off");
@@ -160,11 +233,11 @@ assert.equal(firstFrame.activeLine, 1);
assert.equal(firstFrame.visibleMotion.length, 1);
assert.equal(firstDro.apiName, "real-browser-simulation-dro-state");
assert.equal(firstDro.actual.x, "0.000");
assert.equal(firstDro.distanceToGo.x, "n/a");
assert.equal(firstDro.workOffsetG54.x, "n/a");
assert.equal(firstDro.g92Offset.x, "n/a");
assert.equal(firstDro.toolLengthOffset.x, "n/a");
assert.equal(firstDro.velocity, "n/a");
assert.equal(firstDro.distanceToGo.x, "0.000");
assert.equal(firstDro.workOffsetG54.x, "0.000");
assert.equal(firstDro.g92Offset.x, "0.000");
assert.equal(firstDro.toolLengthOffset.x, "0.000");
assert.equal(firstDro.velocity, "0.000");
const arcFrame = createPlaybackFrame(playbackState, 1);
assert.equal(arcFrame.index, 1);