完善虚拟HAL仿真替代能力
This commit is contained in:
@@ -225,6 +225,26 @@
|
||||
if (!api?.getMachineStatusState) {
|
||||
throw new Error("simulation API missing AXIS machine status controls");
|
||||
}
|
||||
if (
|
||||
!api?.getVirtualHalState ||
|
||||
!api?.applyVirtualHalAction ||
|
||||
!api?.getVirtualHalDroState ||
|
||||
!api?.getVirtualHalLimitsHomeState ||
|
||||
!api?.getVirtualHalMachineStatusState ||
|
||||
!api?.getVirtualHalPinRegistry ||
|
||||
!api?.readVirtualHalPin ||
|
||||
!api?.writeVirtualHalPin ||
|
||||
!api?.applyVirtualHalPinUpdates ||
|
||||
!api?.getVirtualHalIntegrityReport ||
|
||||
!api?.executeVirtualHalcmd ||
|
||||
!api?.executeVirtualHalCommand ||
|
||||
!api?.stepVirtualHalMotionController ||
|
||||
!api?.getVirtualHalSimulationRuntimeReport ||
|
||||
!api?.getVirtualHalSimulationReplacementReport ||
|
||||
!api?.getVirtualRealtimeHalRuntimeReport
|
||||
) {
|
||||
throw new Error("simulation API missing browser virtual HAL controls");
|
||||
}
|
||||
if (!api?.getMachineSessionLoadState || !api?.loadReadyMachineSession) {
|
||||
throw new Error("simulation API missing AXIS machine/session load controls");
|
||||
}
|
||||
@@ -278,7 +298,7 @@
|
||||
throw new Error(`simulation initial built-in run should populate recent programs: ${JSON.stringify(initialRecentPrograms)}`);
|
||||
}
|
||||
api.clearRecentPrograms();
|
||||
if (api.getRecentPrograms().length !== 0 || doc.body.dataset.recentProgramCount !== "0" || !doc.querySelector("[data-recent-program-list]")?.textContent.includes("n/a")) {
|
||||
if (api.getRecentPrograms().length !== 0 || doc.body.dataset.recentProgramCount !== "0" || !doc.querySelector("[data-recent-program-list]")?.textContent.includes("No recent programs")) {
|
||||
throw new Error(`simulation recent programs clear did not reset API/DOM state: ${JSON.stringify(api.getRecentPrograms())}`);
|
||||
}
|
||||
if (!doc.querySelector("[data-open-program]") || !doc.querySelector("[data-open-program-file]")) {
|
||||
@@ -296,8 +316,8 @@
|
||||
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]")) {
|
||||
throw new Error("simulation page missing machine/session readiness controls");
|
||||
}
|
||||
if (!doc.querySelector("[data-axis-machine-control-state]")?.textContent.includes("not bound")) {
|
||||
throw new Error("simulation page should explicitly mark unbound real-machine controls");
|
||||
if (!doc.querySelector("[data-axis-machine-control-state]")?.textContent.includes("browser virtual HAL active")) {
|
||||
throw new Error("simulation page should explicitly mark browser virtual HAL machine controls");
|
||||
}
|
||||
if (!doc.querySelector("[data-mdi-program-text]") || !doc.querySelector("[data-load-mdi-program]") || !doc.querySelector("[data-run-mdi-program]") || !doc.querySelector("[data-mdi-history-list]") || !doc.querySelector("[data-clear-mdi-history]")) {
|
||||
throw new Error("simulation page missing MDI program text controls");
|
||||
@@ -319,10 +339,10 @@
|
||||
const initialMachineStatus = api.getMachineStatusState();
|
||||
if (
|
||||
initialMachineStatus?.source !== "linuxcnc-canonical-events" ||
|
||||
initialMachineStatus.spindle.state !== "n/a" ||
|
||||
doc.querySelector('[data-machine-status="spindle-state"]')?.textContent !== "n/a"
|
||||
initialMachineStatus.spindle.state !== "off" ||
|
||||
doc.querySelector('[data-machine-status="spindle-state"]')?.textContent !== "off"
|
||||
) {
|
||||
throw new Error(`simulation initial machine status should expose explicit unavailable state: ${JSON.stringify(initialMachineStatus)}`);
|
||||
throw new Error(`simulation initial machine status should expose AXIS default state: ${JSON.stringify(initialMachineStatus)}`);
|
||||
}
|
||||
const modalState = api.getModalState();
|
||||
if (modalState?.source !== "linuxcnc-update-tag" || !modalState.raw?.includes("canon_event=UPDATE_TAG")) {
|
||||
@@ -334,7 +354,7 @@
|
||||
}
|
||||
const diagnosticsState = api.getDiagnosticsState();
|
||||
if (
|
||||
diagnosticsState.lastEvent === "n/a" ||
|
||||
diagnosticsState.lastEvent === "pending" ||
|
||||
diagnosticsState.lastError !== "none" ||
|
||||
!doc.querySelector('[data-diagnostics="last-event"]')?.textContent.includes("canon_event=")
|
||||
) {
|
||||
@@ -346,18 +366,39 @@
|
||||
}
|
||||
const limitsHome = api.getLimitsHomeState();
|
||||
if (
|
||||
limitsHome.source !== "unavailable" ||
|
||||
limitsHome.axes.x.homed !== "n/a" ||
|
||||
limitsHome.source !== "browser-virtual-hal" ||
|
||||
limitsHome.axes.x.homed !== "no" ||
|
||||
doc.querySelector('[data-limits-home-row="x"]')?.children.length !== 5 ||
|
||||
!doc.querySelector("[data-limits-home-summary]")?.textContent.includes("source unavailable")
|
||||
!doc.querySelector("[data-limits-home-summary]")?.textContent.includes("source browser-virtual-hal")
|
||||
) {
|
||||
throw new Error(`simulation limits/home state should remain explicit unavailable: ${JSON.stringify(limitsHome)}`);
|
||||
throw new Error(`simulation limits/home state should expose browser virtual HAL rows: ${JSON.stringify(limitsHome)}`);
|
||||
}
|
||||
const virtualHalInitial = api.getVirtualHalState();
|
||||
if (
|
||||
virtualHalInitial.source !== "browser-virtual-hal" ||
|
||||
virtualHalInitial.component !== "axisui" ||
|
||||
virtualHalInitial.task.state !== "ESTOP" ||
|
||||
!virtualHalInitial.axisuiPins.some(({ name }) => name === "jog.x") ||
|
||||
!virtualHalInitial.axisuiPins.some(({ name }) => name === "jog.increment")
|
||||
) {
|
||||
throw new Error(`simulation virtual HAL initial state drift: ${JSON.stringify(virtualHalInitial)}`);
|
||||
}
|
||||
const initialVirtualHalRegistry = api.getVirtualHalPinRegistry();
|
||||
if (
|
||||
initialVirtualHalRegistry.apiName !== "linuxcnc-wasm-virtual-hal-pin-registry" ||
|
||||
!initialVirtualHalRegistry.byName["halui.machine.on"]?.writable ||
|
||||
!initialVirtualHalRegistry.byName["motion.tooloffset.z"]?.writable ||
|
||||
!api.getVirtualHalIntegrityReport().complete
|
||||
) {
|
||||
throw new Error(`simulation virtual HAL registry/integrity drift: ${JSON.stringify(initialVirtualHalRegistry)}`);
|
||||
}
|
||||
const diagnosticsArtifact = api.exportDiagnosticsArtifact();
|
||||
if (
|
||||
diagnosticsArtifact.apiName !== "real-browser-simulation-diagnostics-artifact" ||
|
||||
diagnosticsArtifact.statusHistory.length === 0 ||
|
||||
diagnosticsArtifact.limitsHome.source !== "unavailable" ||
|
||||
diagnosticsArtifact.virtualHal.source !== "browser-virtual-hal" ||
|
||||
diagnosticsArtifact.virtualHalSimulationReplacement?.ready !== true ||
|
||||
diagnosticsArtifact.limitsHome.source !== "browser-virtual-hal" ||
|
||||
diagnosticsArtifact.preview.renderer !== "threejs" ||
|
||||
diagnosticsArtifact.toolTable.state !== "not-loaded"
|
||||
) {
|
||||
@@ -368,12 +409,12 @@
|
||||
throw new Error(`simulation DRO source drift: ${JSON.stringify(droState)}`);
|
||||
}
|
||||
for (const field of ["distanceToGo", "workOffsetG54", "g92Offset", "toolLengthOffset"]) {
|
||||
if (droState[field].x !== "n/a") {
|
||||
throw new Error(`simulation DRO ${field} should remain explicit n/a when unavailable`);
|
||||
if (droState[field].x !== "0.000") {
|
||||
throw new Error(`simulation DRO ${field} should use zeroed AXIS default when unavailable`);
|
||||
}
|
||||
}
|
||||
if (droState.velocity !== "n/a" || doc.querySelector("[data-dro-velocity]")?.textContent !== "n/a") {
|
||||
throw new Error("simulation DRO velocity should remain explicit n/a when unavailable");
|
||||
if (droState.velocity !== "0.000" || doc.querySelector("[data-dro-velocity]")?.textContent !== "0.000") {
|
||||
throw new Error("simulation DRO velocity should use zeroed AXIS default when unavailable");
|
||||
}
|
||||
const previewExtents = doc.querySelector("[data-preview-extents]");
|
||||
const previewOrigin = doc.querySelector("[data-preview-origin]");
|
||||
@@ -619,6 +660,83 @@
|
||||
if (doc.querySelector('[data-axis-panel="preview"]')?.hidden || doc.body.dataset.lastShortcut !== "F6 Preview") {
|
||||
throw new Error("AXIS-style F6 shortcut did not switch to Preview");
|
||||
}
|
||||
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "F1", bubbles: true, cancelable: true }));
|
||||
if (api.getVirtualHalState().task.state !== "ESTOP_RESET" || doc.body.dataset.virtualHalTaskState !== "ESTOP_RESET" || doc.body.dataset.lastShortcut !== "F1 ESTOP") {
|
||||
throw new Error(`AXIS-style F1 did not reset browser virtual HAL ESTOP: ${JSON.stringify(api.getVirtualHalState())}`);
|
||||
}
|
||||
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "F2", bubbles: true, cancelable: true }));
|
||||
if (api.getVirtualHalState().task.state !== "ON" || doc.body.dataset.virtualHalTaskState !== "ON" || doc.body.dataset.lastShortcut !== "F2 Power") {
|
||||
throw new Error(`AXIS-style F2 did not power browser virtual HAL machine: ${JSON.stringify(api.getVirtualHalState())}`);
|
||||
}
|
||||
doc.body.dispatchEvent(new frame.contentWindow.KeyboardEvent("keydown", { key: "Home", ctrlKey: true, bubbles: true, cancelable: true }));
|
||||
if (api.getVirtualHalLimitsHomeState().axes.x.homed !== "yes" || doc.body.dataset.lastShortcut !== "Ctrl-Home Home All") {
|
||||
throw new Error(`AXIS-style Ctrl-Home did not home browser virtual HAL axes: ${JSON.stringify(api.getVirtualHalLimitsHomeState())}`);
|
||||
}
|
||||
doc.querySelector('[data-jog-increment]').value = "0.01 in";
|
||||
doc.querySelector('[data-axis-jog="+"]')?.click();
|
||||
if (api.getVirtualHalState().pins["jog.x"] !== true || api.getVirtualHalDroState().actual.x !== "0.010" || doc.querySelector('[data-dro-actual="x"]')?.textContent !== "0.010") {
|
||||
throw new Error(`AXIS-style jog did not update browser virtual HAL DRO/pins: ${JSON.stringify(api.getVirtualHalState())}`);
|
||||
}
|
||||
doc.querySelector('[data-axis-spindle="cw"]')?.click();
|
||||
doc.querySelector('[data-axis-coolant="flood"]').checked = true;
|
||||
doc.querySelector('[data-axis-coolant="flood"]').dispatchEvent(new frame.contentWindow.Event("change", { bubbles: true }));
|
||||
const virtualMachineStatus = api.getVirtualHalMachineStatusState();
|
||||
if (
|
||||
virtualMachineStatus.spindle.state !== "on" ||
|
||||
virtualMachineStatus.spindle.direction !== "cw" ||
|
||||
virtualMachineStatus.coolant.flood !== "on" ||
|
||||
doc.querySelector('[data-axis-spindle-status]')?.dataset.state !== "ready" ||
|
||||
doc.querySelector('[data-axis-coolant-status]')?.dataset.state !== "ready"
|
||||
) {
|
||||
throw new Error(`AXIS-style spindle/coolant did not update browser virtual HAL machine status: ${JSON.stringify(virtualMachineStatus)}`);
|
||||
}
|
||||
api.writeVirtualHalPin({ name: "spindle.0.speed-out", type: "HAL_FLOAT", value: 1500 });
|
||||
api.applyVirtualHalPinUpdates([
|
||||
{ name: "motion.tooloffset.z", type: "HAL_FLOAT", value: 1.5 },
|
||||
{ name: "halui.tool.number", type: "HAL_U32", value: 8 },
|
||||
]);
|
||||
if (
|
||||
api.readVirtualHalPin("spindle.0.speed-out")?.value !== 1500 ||
|
||||
api.readVirtualHalPin("motion.tooloffset.z")?.value !== 1.5 ||
|
||||
api.readVirtualHalPin("halui.tool.number")?.value !== 8 ||
|
||||
api.getVirtualHalIntegrityReport().complete !== true
|
||||
) {
|
||||
throw new Error(`AXIS-style virtual HAL pin service did not update/read pins: ${JSON.stringify(api.getVirtualHalPinRegistry())}`);
|
||||
}
|
||||
const virtualHalcmdResult = api.executeVirtualHalcmd("loadrt trivkins\naddf motion-controller servo-thread\nsetp axis.x.pos-cmd 1.75\nshow pin axis.x.*");
|
||||
if (
|
||||
virtualHalcmdResult.apiName !== "linuxcnc-wasm-virtual-halcmd-result" ||
|
||||
virtualHalcmdResult.ok !== true ||
|
||||
!virtualHalcmdResult.output.includes("axis.x.pos-cmd") ||
|
||||
api.readVirtualHalPin("axis.x.pos-cmd")?.value !== 1.75
|
||||
) {
|
||||
throw new Error(`AXIS-style virtual halcmd did not execute: ${JSON.stringify(virtualHalcmdResult)}`);
|
||||
}
|
||||
const motionControllerStep = api.stepVirtualHalMotionController({
|
||||
target: { x: 2.25 },
|
||||
maxVelocity: 10,
|
||||
dt: 0.1,
|
||||
cycleCount: 2,
|
||||
});
|
||||
if (
|
||||
motionControllerStep.apiName !== "linuxcnc-wasm-virtual-motion-controller-step" ||
|
||||
motionControllerStep.ok !== true ||
|
||||
motionControllerStep.state.position.x <= 1.75
|
||||
) {
|
||||
throw new Error(`AXIS-style virtual motion controller did not step: ${JSON.stringify(motionControllerStep)}`);
|
||||
}
|
||||
const virtualHalReplacement = api.getVirtualHalSimulationReplacementReport();
|
||||
if (
|
||||
virtualHalReplacement.apiName !== "linuxcnc-wasm-virtual-hal-simulation-replacement-report" ||
|
||||
virtualHalReplacement.ready !== true ||
|
||||
virtualHalReplacement.replacements["linuxcnc-realtime-hal"].ready !== true ||
|
||||
virtualHalReplacement.replacements.halcmd.ready !== true ||
|
||||
virtualHalReplacement.replacements["motion-controller"].ready !== true ||
|
||||
api.getVirtualRealtimeHalRuntimeReport().ready !== true ||
|
||||
api.getVirtualHalSimulationRuntimeReport().replacesHostRuntimeForSimulation !== true
|
||||
) {
|
||||
throw new Error(`AXIS-style virtual HAL simulation replacement report drift: ${JSON.stringify(virtualHalReplacement)}`);
|
||||
}
|
||||
const urlView = api.applyAxisViewFromUrl("?axis_left=mdi&axis_right=dro");
|
||||
if (
|
||||
urlView.left !== "mdi" ||
|
||||
@@ -899,7 +1017,7 @@
|
||||
throw new Error("MDI history item did not restore text into the MDI pane");
|
||||
}
|
||||
api.clearMdiHistory();
|
||||
if (api.getMdiHistory().length !== 0 || doc.body.dataset.mdiHistoryCount !== "0" || !doc.querySelector("[data-mdi-history-list]")?.textContent.includes("n/a")) {
|
||||
if (api.getMdiHistory().length !== 0 || doc.body.dataset.mdiHistoryCount !== "0" || !doc.querySelector("[data-mdi-history-list]")?.textContent.includes("No MDI history")) {
|
||||
throw new Error(`MDI history clear did not reset API/DOM state: ${JSON.stringify(api.getMdiHistory())}`);
|
||||
}
|
||||
|
||||
@@ -1080,7 +1198,7 @@ TOOL_TABLE = tool.tbl
|
||||
if (
|
||||
loadedDiagnosticsArtifact.toolTable.state !== "loaded" ||
|
||||
loadedDiagnosticsArtifact.toolTable.rows.length !== 1 ||
|
||||
loadedDiagnosticsArtifact.limitsHome.axes.x.fault !== "n/a" ||
|
||||
loadedDiagnosticsArtifact.limitsHome.axes.x.fault !== "none" ||
|
||||
!loadedDiagnosticsArtifact.statusHistory.some(({ kind }) => kind === "session")
|
||||
) {
|
||||
throw new Error(`simulation diagnostics artifact did not include session/tool/limits state: ${JSON.stringify(loadedDiagnosticsArtifact)}`);
|
||||
@@ -1157,7 +1275,7 @@ TOOL_TABLE = tool.tbl
|
||||
if (api.getRunMode().mode !== "standalone" || doc.body.dataset.runMode !== "standalone") {
|
||||
throw new Error("simulation run mode should stay standalone after disabled-session run");
|
||||
}
|
||||
if (api.getRunSummary().executionMode !== "linuxcnc-wasm" || doc.querySelector('[data-run-summary="session-ini"]')?.textContent !== "n/a") {
|
||||
if (api.getRunSummary().executionMode !== "linuxcnc-wasm" || doc.querySelector('[data-run-summary="session-ini"]')?.textContent !== "-") {
|
||||
throw new Error(`simulation run summary should return to standalone details: ${JSON.stringify(api.getRunSummary())}`);
|
||||
}
|
||||
if (api.getRunControlState().reason !== "Loaded machine session bypassed by Use Session toggle") {
|
||||
|
||||
@@ -126,6 +126,19 @@ for (const phrase of [
|
||||
"browser_release_artifact_url_workflow_smoke=ok",
|
||||
"browser_real_simulation_page_smoke=ok",
|
||||
"project_release_handoff_docs_node_smoke=ok",
|
||||
"Project-level virtual HAL",
|
||||
"createVirtualHalState()",
|
||||
"applyVirtualHalAction()",
|
||||
"createLinuxCncVirtualHalRuntime()",
|
||||
"createVirtualHalWasmBridgeSnapshot()",
|
||||
"createVirtualHalPinInventory()",
|
||||
"createVirtualHalBridgeReadiness()",
|
||||
"createVirtualHalBridgeActionPlan()",
|
||||
"createVirtualHalProjectReport()",
|
||||
"applyVirtualHalToInterpSdk()",
|
||||
"VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS",
|
||||
"VIRTUAL_HAL_PROJECT_PIN_GROUPS",
|
||||
"realtime HAL runtime",
|
||||
"L4-USER-M-PROCESS",
|
||||
"L4-TOOL-DB",
|
||||
"L4-PYTHON-REMAP",
|
||||
@@ -176,6 +189,19 @@ for (const phrase of [
|
||||
|
||||
for (const phrase of [
|
||||
"OPFS/session persistence exports",
|
||||
"Project-level virtual HAL boundary",
|
||||
"createVirtualHalState()",
|
||||
"applyVirtualHalAction()",
|
||||
"createLinuxCncVirtualHalRuntime()",
|
||||
"createVirtualHalWasmBridgeSnapshot()",
|
||||
"createVirtualHalPinInventory()",
|
||||
"createVirtualHalBridgeReadiness()",
|
||||
"createVirtualHalBridgeActionPlan()",
|
||||
"createVirtualHalProjectReport()",
|
||||
"applyVirtualHalToInterpSdk()",
|
||||
"VIRTUAL_HAL_WASM_BRIDGE_FUNCTIONS",
|
||||
"VIRTUAL_HAL_PROJECT_PIN_GROUPS",
|
||||
"realtime HAL runtime",
|
||||
"Project release readiness export",
|
||||
"createProjectReleaseGateManifest()",
|
||||
"createProjectReleaseGateResultMatrix()",
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,7 +4,9 @@ import { dirname, resolve } from "node:path";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
applyVirtualHalAction,
|
||||
createLinuxCncInterpSdk,
|
||||
createVirtualHalState,
|
||||
planIniFileContextStaging,
|
||||
} from "../../../runtime/sdk/src/index.js";
|
||||
import {
|
||||
@@ -726,6 +728,46 @@ verifyExpectedOutput(
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
if (
|
||||
interp.hasWasmFunction("lcinterp_hal_reset") &&
|
||||
interp.hasWasmFunction("lcinterp_hal_set_value") &&
|
||||
interp.hasWasmFunction("lcinterp_probe_hal_named")
|
||||
) {
|
||||
let virtualHal = createVirtualHalState();
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "estop");
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "power");
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "jog", { axis: "x", increment: "0.01 in", direction: 1 });
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "spindle", { command: "cw", speed: 1200 });
|
||||
virtualHal = applyVirtualHalAction(virtualHal, "coolant", { name: "flood", enabled: true });
|
||||
const applyResult = interp.applyVirtualHalState(virtualHal);
|
||||
assert.equal(applyResult.apiName, "linuxcnc-wasm-hal-apply-result");
|
||||
assert.equal(applyResult.source, "browser-virtual-hal");
|
||||
assert.ok(applyResult.applied > 0, "virtual HAL bridge should apply values");
|
||||
verifyExpectedOutput(
|
||||
"probe_virtual_hal_bridge",
|
||||
[
|
||||
interp.probeHalNamed("_hal[jog.x]"),
|
||||
interp.probeHalNamed("_hal[jog.increment]"),
|
||||
interp.probeHalNamed("_hal[axisui.jog.x]"),
|
||||
interp.probeHalNamed("_hal[axisui.jog.increment]"),
|
||||
interp.probeHalNamed("_hal[axis.x.pos-cmd]"),
|
||||
interp.probeHalNamed("_hal[halui.machine.is-on]"),
|
||||
interp.probeHalNamed("_hal[spindle.0.speed-out]"),
|
||||
interp.probeHalNamed("_hal[iocontrol.0.coolant-flood]"),
|
||||
].join(""),
|
||||
[
|
||||
"_hal[jog.x]: rc=0 found=1 value=1",
|
||||
"_hal[jog.increment]: rc=0 found=1 value=0.01",
|
||||
"_hal[axisui.jog.x]: rc=0 found=1 value=1",
|
||||
"_hal[axisui.jog.increment]: rc=0 found=1 value=0.01",
|
||||
"_hal[axis.x.pos-cmd]: rc=0 found=1 value=0.01",
|
||||
"_hal[halui.machine.is-on]: rc=0 found=1 value=1",
|
||||
"_hal[spindle.0.speed-out]: rc=0 found=1 value=1200",
|
||||
"_hal[iocontrol.0.coolant-flood]: rc=0 found=1 value=1",
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
for (const fixtureName of INTERP_MDI_FIXTURES) {
|
||||
const programText = readFileSync(
|
||||
resolve(rootDir, `tests/fixtures/gcode/${fixtureName}.ngc`),
|
||||
|
||||
Reference in New Issue
Block a user