Validate AXIS task state flow
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { createServer } from "node:http";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
|
||||
const rootDir = path.resolve(import.meta.dirname, "../../..");
|
||||
const projectDir = path.join(rootDir, "web-rtcp-5axis-xyzbc-trt-sim-plan");
|
||||
const requireFromApp = createRequire(path.join(projectDir, "app/package.json"));
|
||||
const { chromium } = requireFromApp("playwright");
|
||||
|
||||
const server = createServer(async (request, response) => {
|
||||
try {
|
||||
const url = new URL(request.url || "/", "http://127.0.0.1");
|
||||
const pathname = decodeURIComponent(url.pathname);
|
||||
const filePath = path.join(rootDir, pathname);
|
||||
if (!filePath.startsWith(rootDir)) {
|
||||
response.writeHead(403).end("forbidden");
|
||||
return;
|
||||
}
|
||||
const body = await readFile(filePath);
|
||||
response.writeHead(200, { "content-type": contentType(filePath) });
|
||||
response.end(body);
|
||||
} catch (error) {
|
||||
response.writeHead(404).end(error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
const port = server.address().port;
|
||||
const browser = await chromium.launch({
|
||||
headless: true,
|
||||
executablePath: process.env.CHROMIUM || undefined,
|
||||
args: ["--disable-gpu", "--no-sandbox", "--enable-unsafe-swiftshader"],
|
||||
});
|
||||
|
||||
try {
|
||||
const page = await browser.newPage();
|
||||
const testUrl = `http://127.0.0.1:${port}/web-rtcp-5axis-xyzbc-trt-sim-plan/tests/browser/xyzbc_trt_all_buttons.html?app=../../app/dist/index.html`;
|
||||
await page.goto(testUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
|
||||
await page.waitForFunction(() => {
|
||||
const text = document.querySelector("#result")?.textContent || "";
|
||||
return text.startsWith("xyzbc_trt_all_buttons=ok") || text.startsWith("xyzbc_trt_all_buttons=fail");
|
||||
}, null, { timeout: 120000 });
|
||||
const result = await page.locator("#result").textContent();
|
||||
if (!result?.startsWith("xyzbc_trt_all_buttons=ok")) {
|
||||
throw new Error(result || "all button test did not report a result");
|
||||
}
|
||||
console.log(result.trim());
|
||||
} finally {
|
||||
await browser.close();
|
||||
await new Promise((resolve) => server.close(resolve));
|
||||
}
|
||||
|
||||
function contentType(filePath) {
|
||||
if (filePath.endsWith(".html")) return "text/html; charset=utf-8";
|
||||
if (filePath.endsWith(".js") || filePath.endsWith(".mjs")) return "text/javascript; charset=utf-8";
|
||||
if (filePath.endsWith(".css")) return "text/css; charset=utf-8";
|
||||
if (filePath.endsWith(".json")) return "application/json; charset=utf-8";
|
||||
if (filePath.endsWith(".wasm")) return "application/wasm";
|
||||
if (filePath.endsWith(".svg")) return "image/svg+xml";
|
||||
if (filePath.endsWith(".png")) return "image/png";
|
||||
return "application/octet-stream";
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
PROJECT_DIR="$ROOT_DIR/web-rtcp-5axis-xyzbc-trt-sim-plan"
|
||||
|
||||
npm --prefix "$PROJECT_DIR/app" run build >/dev/null
|
||||
node "$PROJECT_DIR/tests/browser/verify_xyzbc_trt_all_buttons.mjs"
|
||||
@@ -0,0 +1,344 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>xyzbc-trt all button coverage</title>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="result">xyzbc_trt_all_buttons=pending</pre>
|
||||
<iframe id="app-frame" title="xyzbc-trt app"></iframe>
|
||||
<script type="module">
|
||||
const result = document.querySelector("#result");
|
||||
const frame = document.querySelector("#app-frame");
|
||||
const appSrc = new URLSearchParams(window.location.search).get("app") || "../../app/index.html";
|
||||
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
async function runAllButtons() {
|
||||
result.textContent = "xyzbc_trt_all_buttons=loading-frame";
|
||||
const frameLoaded = new Promise((resolve, reject) => {
|
||||
frame.addEventListener("load", resolve, { once: true });
|
||||
frame.addEventListener("error", reject, { once: true });
|
||||
});
|
||||
frame.src = appSrc;
|
||||
await frameLoaded;
|
||||
|
||||
result.textContent = "xyzbc_trt_all_buttons=waiting-api";
|
||||
const win = frame.contentWindow;
|
||||
const doc = frame.contentDocument;
|
||||
const runtimeErrors = [];
|
||||
win.addEventListener("error", (event) => {
|
||||
runtimeErrors.push(event.message || String(event.error || "window error"));
|
||||
});
|
||||
win.addEventListener("unhandledrejection", (event) => {
|
||||
runtimeErrors.push(event.reason?.message || String(event.reason || "unhandled rejection"));
|
||||
});
|
||||
|
||||
await waitFor(() => win.webRtcp5AxisSimulation, "public simulation API");
|
||||
const api = win.webRtcp5AxisSimulation;
|
||||
result.textContent = "xyzbc_trt_all_buttons=waiting-runtimes";
|
||||
await waitFor(() => {
|
||||
const state = api.getState();
|
||||
return state.kinematicsRuntime?.loaded === true &&
|
||||
state.interpreterRuntime?.loaded === true &&
|
||||
state.taskHalRuntime?.loaded === true;
|
||||
}, "runtime loaded state");
|
||||
result.textContent = "xyzbc_trt_all_buttons=waiting-program";
|
||||
if (api.getState().machineFileStaging?.status !== "staged") {
|
||||
api.dispatch({ type: "STAGE_MACHINE_FILES_REQUEST" });
|
||||
}
|
||||
await waitFor(() => api.getState().machineFileStaging?.status === "staged", "machine file staging");
|
||||
const source = api.getState().machineFileStaging?.gcodeSources?.find((item) => item.filename === "xyzbc_switchkins.ngc") ||
|
||||
api.getState().machineFileStaging?.gcodeSources?.[0];
|
||||
if (source?.sourceRel) {
|
||||
api.dispatch({ type: "LOAD_LINUXCNC_GCODE_SOURCE", sourceRel: source.sourceRel });
|
||||
}
|
||||
await waitFor(() => {
|
||||
const state = api.getState();
|
||||
return state.machineFileStaging?.status === "staged" &&
|
||||
!state.interpreterExecutionPending &&
|
||||
Number(state.programAxisPreviewPath?.sampleCount || 0) > 0;
|
||||
}, "staged default program");
|
||||
|
||||
result.textContent = "xyzbc_trt_all_buttons=checking-dom";
|
||||
assertButtonParityDomCoverage(doc, api.getButtonParity());
|
||||
result.textContent = "xyzbc_trt_all_buttons=checking-menu";
|
||||
await verifyMenuButtons(doc, api);
|
||||
result.textContent = "xyzbc_trt_all_buttons=checking-toolbar";
|
||||
await verifyToolbarButtons(doc, api);
|
||||
result.textContent = "xyzbc_trt_all_buttons=checking-manual-mdi-pyvcp";
|
||||
await verifyManualMdiPyvcpButtons(doc, api);
|
||||
result.textContent = "xyzbc_trt_all_buttons=checking-overrides";
|
||||
await verifyOverrideCoolantAndViewButtons(doc, api);
|
||||
|
||||
if (runtimeErrors.length > 0) {
|
||||
throw new Error(`runtime errors: ${runtimeErrors.join(" | ")}`);
|
||||
}
|
||||
result.textContent = `xyzbc_trt_all_buttons=ok checked=${api.getButtonParity().length}`;
|
||||
}
|
||||
|
||||
function assertButtonParityDomCoverage(doc, buttonParity) {
|
||||
if (!Array.isArray(buttonParity) || buttonParity.length < 50) {
|
||||
throw new Error(`button parity list too small: ${buttonParity?.length}`);
|
||||
}
|
||||
const controls = [...doc.querySelectorAll("[data-action], [data-menu-command]")];
|
||||
for (const item of buttonParity) {
|
||||
const matches = controls.filter((control) => (
|
||||
control.dataset.action === item.action ||
|
||||
control.dataset.menuCommand === item.action
|
||||
));
|
||||
if (matches.length === 0) {
|
||||
throw new Error(`button parity item has no DOM control: ${item.id}/${item.action}`);
|
||||
}
|
||||
if (!matches.some((control) => control.dataset.axisSourceRef && control.dataset.axisExpectedEffect)) {
|
||||
throw new Error(`button parity item has no source-tagged DOM control: ${item.id}/${item.action}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function verifyMenuButtons(doc, api) {
|
||||
await click(doc, '[data-menu-command="stage"]', "menu Stage LinuxCNC files");
|
||||
await waitState(api, (state) => state.machineFileStaging?.status === "staged", "menu stage completed");
|
||||
|
||||
await click(doc, '[data-menu-command="save-session"]', "menu Save session");
|
||||
await waitState(api, (state) => (
|
||||
state.sessionPersistence?.status === "saved" &&
|
||||
Boolean(state.sessionPersistence?.path) &&
|
||||
Boolean(state.sessionPersistence?.savedAt)
|
||||
), "menu save session completed");
|
||||
|
||||
await click(doc, '[data-menu-command="restore-session"]', "menu Restore session");
|
||||
await waitState(api, (state) => (
|
||||
state.sessionPersistence?.status === "restored" &&
|
||||
Boolean(state.sessionPersistence?.path) &&
|
||||
Boolean(state.sessionPersistence?.restoredAt)
|
||||
), "menu restore session completed");
|
||||
|
||||
await click(doc, '[data-menu-command="reload"]', "menu Reload");
|
||||
await waitState(api, (state) => Number(state.programAxisPreviewPath?.sampleCount || 0) > 0, "menu reload restored preview");
|
||||
|
||||
await click(doc, '[data-menu-command="view-z"]', "menu View Z");
|
||||
await waitState(api, (state) => state.preview.selectedView === "z", "menu view z");
|
||||
await click(doc, '[data-menu-command="view-y"]', "menu View Y");
|
||||
await waitState(api, (state) => state.preview.selectedView === "y", "menu view y");
|
||||
await click(doc, '[data-menu-command="view-x"]', "menu View X");
|
||||
await waitState(api, (state) => state.preview.selectedView === "x", "menu view x");
|
||||
await click(doc, '[data-menu-command="view-p"]', "menu View P");
|
||||
await waitState(api, (state) => state.preview.selectedView === "iso", "menu view p");
|
||||
await click(doc, '[data-menu-command="clear-preview"]', "menu Clear Live Plot");
|
||||
await waitState(api, (state) => state.preview.pathPoints === 0, "menu clear preview");
|
||||
|
||||
await click(doc, '[data-menu-command="audit"]', "menu Run parity audit");
|
||||
await waitState(api, (state) => (
|
||||
state.interpreterExecutionPending === false &&
|
||||
Number(state.programAxisPreviewPath?.sampleCount || 0) > 0 &&
|
||||
state.taskHalRuntime?.loaded === true
|
||||
), "menu audit completed observable work");
|
||||
}
|
||||
|
||||
async function verifyToolbarButtons(doc, api) {
|
||||
await ensureResetPoweredHomedManual(doc, api);
|
||||
|
||||
await click(doc, '[data-action="toggle-auto-manual"]', "toolbar AUTO");
|
||||
await waitState(api, (state) => state.machine.mode === "auto", "toolbar auto");
|
||||
await click(doc, '[data-action="toggle-auto-manual"]', "toolbar MANUAL");
|
||||
await waitState(api, (state) => state.machine.mode === "manual", "toolbar manual");
|
||||
|
||||
await click(doc, '[data-action="reload"]', "toolbar reload");
|
||||
await waitState(api, (state) => Number(state.programAxisPreviewPath?.sampleCount || 0) > 0, "toolbar reload");
|
||||
|
||||
await click(doc, '[data-action="run"]', "toolbar run");
|
||||
await waitState(api, (state) => state.runState === "running" || state.runState === "complete", "toolbar run started");
|
||||
await click(doc, '[data-tool-id="tbtn_pause"]', "toolbar pause");
|
||||
await waitState(api, (state) => state.runState === "paused", "toolbar pause");
|
||||
await click(doc, '[data-action="step"]', "toolbar step");
|
||||
await waitState(api, (state) => state.machine.taskPaused === true && Number(state.programExecutionSampleIndex) >= 0, "toolbar step");
|
||||
await click(doc, '[data-tool-id="tbtn_pause"]', "toolbar resume");
|
||||
await waitState(api, (state) => state.runState === "running", "toolbar resume");
|
||||
await click(doc, '[data-action="stop"]', "toolbar stop");
|
||||
await waitState(api, (state) => state.runState === "stopped", "toolbar stop");
|
||||
|
||||
await click(doc, '[data-action="estop"]', "toolbar estop");
|
||||
await waitState(api, (state) => state.machine.estopActive === true, "toolbar estop");
|
||||
await click(doc, '[data-action="estop"]', "toolbar reset");
|
||||
await waitState(api, (state) => state.machine.estopActive === false, "toolbar reset");
|
||||
if (api.getState().machine.powerOn !== true) {
|
||||
await click(doc, '[data-action="power"]', "toolbar power on after reset");
|
||||
await waitState(api, (state) => state.machine.powerOn === true, "toolbar power on after reset");
|
||||
}
|
||||
await click(doc, '[data-action="power"]', "toolbar power off");
|
||||
await waitState(api, (state) => state.machine.powerOn === false, "toolbar power off");
|
||||
await click(doc, '[data-action="power"]', "toolbar power on");
|
||||
await waitState(api, (state) => state.machine.powerOn === true, "toolbar power on");
|
||||
}
|
||||
|
||||
async function verifyManualMdiPyvcpButtons(doc, api) {
|
||||
await ensureResetPoweredHomedManual(doc, api);
|
||||
|
||||
const joint3 = doc.querySelector('[data-action="select-joint"][value="3"]');
|
||||
joint3.checked = true;
|
||||
joint3.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
await waitState(api, (state) => state.machine.selectedJoint === 3 && state.machine.jogAxis === "b", "select B joint");
|
||||
|
||||
const jogIncrement = doc.querySelector('[data-action="jog-increment"]');
|
||||
jogIncrement.value = "0.1";
|
||||
jogIncrement.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
await waitState(api, (state) => Number(state.machine.jogIncrement) === 0.1, "jog increment");
|
||||
|
||||
const beforeJogB = api.getState().axisPose.b;
|
||||
await click(doc, '[data-action="jog-plus"]', "manual jog plus");
|
||||
await waitState(api, (state) => state.axisPose.b > beforeJogB, "manual jog plus changed B");
|
||||
const afterJogPlusB = api.getState().axisPose.b;
|
||||
await click(doc, '[data-action="jog-minus"]', "manual jog minus");
|
||||
await waitState(api, (state) => state.axisPose.b < afterJogPlusB, "manual jog minus changed B");
|
||||
|
||||
await click(doc, '[data-action="touch-off"]', "manual touch off");
|
||||
await waitState(api, (state) => state.mdiHistory[0] === "G10 L20 P0 B0" && state.machine.mode === "manual", "manual touch off");
|
||||
await click(doc, '[data-action="tool-touch-off"]', "manual tool touch off");
|
||||
await waitState(api, (state) => state.mdiHistory[0] === "G43" && state.machine.mode === "manual", "manual tool touch off");
|
||||
|
||||
await click(doc, '[data-action="tab-mdi"]', "MDI tab");
|
||||
await waitState(api, (state) => state.machine.mode === "mdi", "mdi tab");
|
||||
const mdiInput = doc.querySelector('[data-action="mdi-input"]');
|
||||
mdiInput.value = "G90";
|
||||
mdiInput.dispatchEvent(new Event("input", { bubbles: true }));
|
||||
doc.querySelector('[data-action="mdi-form"]').dispatchEvent(new Event("submit", { bubbles: true, cancelable: true }));
|
||||
await waitState(api, (state) => state.mdiHistory[0] === "G90", "mdi submit");
|
||||
await click(doc, '[data-action="mdi-history"][data-command="G90"]', "MDI history G90");
|
||||
await waitState(api, (state) => state.mdiHistory[0] === "G90", "mdi history");
|
||||
|
||||
await click(doc, '[data-action="kins-tcp"]', "PyVCP TCP");
|
||||
await waitState(api, (state) => state.kinsType === "tcp-xyzbc" && state.mdiHistory[0] === "M428", "pyvcp tcp");
|
||||
await click(doc, '[data-action="kins-identity"]', "PyVCP identity");
|
||||
await waitState(api, (state) => state.kinsType === "identity" && state.mdiHistory[0] === "M429", "pyvcp identity");
|
||||
await click(doc, '[data-action="kins-userk"]', "PyVCP userk");
|
||||
await waitState(api, (state) => state.kinsType === "userk" && state.mdiHistory[0] === "M430", "pyvcp userk");
|
||||
}
|
||||
|
||||
async function verifyOverrideCoolantAndViewButtons(doc, api) {
|
||||
await ensureResetPoweredHomedManual(doc, api);
|
||||
|
||||
const feedBefore = api.getState().feed.feedOverride;
|
||||
await click(doc, '[data-action="feed-override-up"]', "feed override up");
|
||||
await waitState(api, (state) => state.feed.feedOverride === Math.min(feedBefore + 10, 200), "feed override up");
|
||||
await click(doc, '[data-action="feed-override-down"]', "feed override down");
|
||||
await waitState(api, (state) => state.feed.feedOverride === feedBefore, "feed override down");
|
||||
|
||||
const rapidBefore = api.getState().feed.rapidOverride;
|
||||
await click(doc, '[data-action="rapid-override-up"]', "rapid override up");
|
||||
await waitState(api, (state) => state.feed.rapidOverride === Math.min(rapidBefore + 10, 200), "rapid override up");
|
||||
await click(doc, '[data-action="rapid-override-down"]', "rapid override down");
|
||||
await waitState(api, (state) => state.feed.rapidOverride === rapidBefore, "rapid override down");
|
||||
|
||||
const spindleOverrideBefore = api.getState().spindle.override;
|
||||
await click(doc, '[data-action="spindle-override-up"]', "spindle override up");
|
||||
await waitState(api, (state) => state.spindle.override === Math.min(spindleOverrideBefore + 10, 150), "spindle override up");
|
||||
await click(doc, '[data-action="spindle-override-down"]', "spindle override down");
|
||||
await waitState(api, (state) => state.spindle.override === spindleOverrideBefore, "spindle override down");
|
||||
|
||||
await click(doc, '[data-action="spindle-reverse"]', "spindle reverse");
|
||||
await waitState(api, (state) => state.spindle.direction === "reverse" && state.spindle.enabled === true, "spindle reverse");
|
||||
await click(doc, '[data-action="spindle-stop"]', "spindle stop");
|
||||
await waitState(api, (state) => state.spindle.direction === "stop" && state.spindle.enabled === false, "spindle stop");
|
||||
await click(doc, '[data-action="spindle-forward"]', "spindle forward");
|
||||
await waitState(api, (state) => state.spindle.direction === "forward" && state.spindle.enabled === true, "spindle forward");
|
||||
|
||||
await click(doc, '[data-action="toggle-flood"]', "toggle flood");
|
||||
await waitState(api, (state) => state.coolant.flood === true, "toggle flood");
|
||||
await click(doc, '[data-action="toggle-mist"]', "toggle mist");
|
||||
await waitState(api, (state) => state.coolant.mist === true, "toggle mist");
|
||||
await click(doc, '[data-action="block-delete"]', "block delete");
|
||||
await waitState(api, (state) => state.gmoccapyGui.optionalBlocks === true, "block delete");
|
||||
await click(doc, '[data-action="optional-stop"]', "optional stop");
|
||||
await waitState(api, (state) => state.gmoccapyGui.optionalStop === true, "optional stop");
|
||||
await click(doc, '[data-action="ignore-limits"]', "ignore limits");
|
||||
await waitState(api, (state) => state.gmoccapyGui.ignoreLimits === true, "ignore limits");
|
||||
|
||||
await click(doc, '[data-action="view-z"]', "view z");
|
||||
await waitState(api, (state) => state.preview.selectedView === "z", "view z");
|
||||
await click(doc, '[data-action="view-y"]', "view y");
|
||||
await waitState(api, (state) => state.preview.selectedView === "y", "view y");
|
||||
await click(doc, '[data-action="view-x"]', "view x");
|
||||
await waitState(api, (state) => state.preview.selectedView === "x", "view x");
|
||||
await click(doc, '[data-action="view-p"]', "view p");
|
||||
await waitState(api, (state) => state.preview.selectedView === "iso", "view p");
|
||||
await click(doc, '[data-action="clear-preview"]', "clear preview");
|
||||
await waitState(api, (state) => state.preview.pathPoints === 0, "clear preview");
|
||||
await click(doc, '[data-action="reload"]', "reload after clear preview");
|
||||
await waitState(api, (state) => state.preview.pathPoints > 0, "reload after clear preview");
|
||||
}
|
||||
|
||||
async function ensureResetPoweredHomedManual(doc, api) {
|
||||
let state = api.getState();
|
||||
if (state.machine.estopActive === true || state.machine.taskState === "estop") {
|
||||
await click(doc, '[data-action="estop"]', "ensure reset estop");
|
||||
await waitState(api, (next) => next.machine.estopActive === false, "ensure estop reset");
|
||||
}
|
||||
state = api.getState();
|
||||
if (state.machine.powerOn !== true) {
|
||||
await click(doc, '[data-action="power"]', "ensure power on");
|
||||
await waitState(api, (next) => next.machine.powerOn === true, "ensure power on");
|
||||
}
|
||||
if (api.getState().machine.mode !== "manual") {
|
||||
await click(doc, '[data-action="tab-manual"]', "ensure manual before home");
|
||||
await waitState(api, (next) => next.machine.mode === "manual", "ensure manual before home");
|
||||
}
|
||||
state = api.getState();
|
||||
if (state.machine.allHomed !== true) {
|
||||
await click(doc, '[data-action="home-all"]', "ensure home all");
|
||||
await waitState(api, (next) => next.machine.allHomed === true, "ensure home all");
|
||||
}
|
||||
if (api.getState().machine.mode !== "manual") {
|
||||
await click(doc, '[data-action="tab-manual"]', "ensure manual");
|
||||
await waitState(api, (next) => next.machine.mode === "manual", "ensure manual mode");
|
||||
}
|
||||
if (api.getState().runState === "running" || api.getState().runState === "paused" || api.getState().runState === "stepping") {
|
||||
await click(doc, '[data-action="stop"]', "ensure stopped");
|
||||
await waitState(api, (next) => next.runState === "stopped" || next.runState === "idle", "ensure stopped");
|
||||
await click(doc, '[data-action="tab-manual"]', "manual after stop");
|
||||
}
|
||||
}
|
||||
|
||||
async function click(doc, selector, label) {
|
||||
const element = await waitFor(() => doc.querySelector(selector), label);
|
||||
element.click();
|
||||
await wait(80);
|
||||
return element;
|
||||
}
|
||||
|
||||
function waitState(api, predicate, label) {
|
||||
return waitFor(() => {
|
||||
const state = api.getState();
|
||||
return predicate(state) ? state : null;
|
||||
}, label, () => summarizeState(api.getState()));
|
||||
}
|
||||
|
||||
async function waitFor(predicate, label, describe = null) {
|
||||
for (let attempt = 0; attempt < 300; attempt += 1) {
|
||||
const value = predicate();
|
||||
if (value) return value;
|
||||
await wait(50);
|
||||
}
|
||||
const suffix = describe ? ` ${describe()}` : "";
|
||||
throw new Error(`timed out waiting for ${label}${suffix}`);
|
||||
}
|
||||
|
||||
function summarizeState(state) {
|
||||
return JSON.stringify({
|
||||
runState: state.runState,
|
||||
mode: state.machine?.mode,
|
||||
taskState: state.machine?.taskState,
|
||||
powerOn: state.machine?.powerOn,
|
||||
allHomed: state.machine?.allHomed,
|
||||
kinsType: state.kinsType,
|
||||
mdiHistory: state.mdiHistory?.slice?.(0, 3),
|
||||
operatorMessage: state.operatorMessage,
|
||||
});
|
||||
}
|
||||
|
||||
runAllButtons().catch((error) => {
|
||||
result.textContent = `xyzbc_trt_all_buttons=fail ${error.stack || error.message || error}`;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -29,7 +29,7 @@ store.dispatch({
|
||||
content: [
|
||||
"G90 G17",
|
||||
"G0 X0 Y0 Z0",
|
||||
"G1 X4 Y5 A6 C7 F100",
|
||||
"G1 X4 Y5 B6 C7 F100",
|
||||
"M2",
|
||||
].join("\n"),
|
||||
});
|
||||
@@ -39,7 +39,7 @@ store.dispatch({ type: "STEP" });
|
||||
const beforeSave = store.getState();
|
||||
const payload = createFiveAxisSessionPayload(beforeSave);
|
||||
assert.equal(payload.apiName, "web-rtcp-5axis-session-payload");
|
||||
assert.equal(payload.machineProfile, "xyzac-trt");
|
||||
assert.equal(payload.machineProfile, "xyzbc-trt");
|
||||
assert.equal(payload.programExecution.sourceMode, "linuxcnc-interpreter-wasm");
|
||||
assert.equal(payload.rtcpState, "on");
|
||||
assert.equal(payload.programRuntimeFeedback.sourceMode, "linuxcnc-tp-runtime-sample");
|
||||
@@ -48,7 +48,7 @@ assert.equal(payload.programRuntimeFeedback.semanticBoundary, "linuxcnc_tp_run_c
|
||||
const storage = createMemorySessionStorage();
|
||||
const saved = await store.saveSession({ storage });
|
||||
assert.equal(saved.snapshot.format, "web-rtcp-5axis-session-snapshot");
|
||||
assert.equal(saved.path, "web-rtcp-5axis-sim-plan/sessions/gmoccapy-web-session/web-rtcp-5axis-session.json");
|
||||
assert.equal(saved.path, "web-rtcp-5axis-xyzbc-trt-sim-plan/sessions/xyzbc-trt-web-session/web-rtcp-5axis-session.json");
|
||||
assert.equal(saved.storageMode, "memory");
|
||||
assert.equal(store.getState().sessionPersistence.storageMode, "memory");
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ store.dispatch({ type: "ESTOP" });
|
||||
state = store.getState();
|
||||
assert.equal(sidebarEntry(state, "estop").status, "emergency");
|
||||
assert.equal(sidebarEntry(state, "power").allowed, false);
|
||||
assert.equal(sidebarEntry(state, "power").operatorMessage, "power on blocked: reset estop first");
|
||||
assert.equal(sidebarEntry(state, "power").operatorMessage, "power blocked: reset ESTOP first");
|
||||
|
||||
store.dispatch({ type: "RESET" });
|
||||
store.dispatch({ type: "TOGGLE_POWER" });
|
||||
@@ -126,7 +126,7 @@ store.dispatch({
|
||||
await store.stageMachineFiles({ storage: createMemorySessionStorage() });
|
||||
state = store.getState();
|
||||
assert.equal(state.machineProject.profileId, "xyzac-trt");
|
||||
assert.equal(state.machineProject.projectRoot, "web-rtcp-5axis-sim-plan/machines/xyzac-trt");
|
||||
assert.equal(state.machineProject.projectRoot, "web-rtcp-5axis-xyzbc-trt-sim-plan/machines/xyzac-trt");
|
||||
assert.equal(state.machineProject.ini.filename, "xyzac-trt.ini");
|
||||
assert.equal(state.machineProject.ini.sourceMatchesProfile, true);
|
||||
assert.equal(state.machineProject.ini.textMatchesLoadedIni, true);
|
||||
|
||||
@@ -59,7 +59,7 @@ let state = store.getState();
|
||||
let matrix = state.linuxCncParityMatrix;
|
||||
|
||||
assert.equal(matrix.apiName, "web-rtcp-5axis-linuxcnc-parity-matrix");
|
||||
assert.equal(matrix.profileId, "xyzac-trt");
|
||||
assert.equal(matrix.profileId, "xyzbc-trt");
|
||||
assert.equal(matrix.status, "implemented");
|
||||
assert.equal(matrix.implementedCount, matrix.itemCount);
|
||||
assert.equal(matrix.rightSidebarComplete, true);
|
||||
@@ -73,11 +73,11 @@ assert.equal(matrix.linuxCncFunctions.includes("M428 TCP"), true);
|
||||
assert.equal(matrix.linuxCncFunctions.includes("M429 identity"), true);
|
||||
assert.equal(matrix.linuxCncFunctions.includes("M430 userk"), true);
|
||||
|
||||
assert.equal(item(matrix, "xyzac-trt-axis-vismach-config").active, true);
|
||||
assert.equal(item(matrix, "xyzac-trt-axis-vismach-config").active, false);
|
||||
assert.equal(item(matrix, "xyzac-trt-axis-vismach-config").sourceMapped, true);
|
||||
assert.equal(item(matrix, "xyzac-trt-axis-vismach-config").linuxCncPaths.some((path) => path.endsWith("xyzac-trt.ini")), true);
|
||||
assert.equal(item(matrix, "xyzbc-trt-axis-vismach-config").implemented, true);
|
||||
assert.equal(item(matrix, "xyzbc-trt-axis-vismach-config").active, false);
|
||||
assert.equal(item(matrix, "xyzbc-trt-axis-vismach-config").active, true);
|
||||
assert.equal(item(matrix, "gmoccapy-trt-config").linuxCncPaths.some((path) => path.includes("gmoccapy/non_trivial_kinematics/table-rotary-tilting/xyzac-trt.ini")), true);
|
||||
assert.equal(item(matrix, "gmoccapy-trt-config").functions.includes("DISPLAY gmoccapy"), true);
|
||||
assert.equal(item(matrix, "gmoccapy-native-operator-ui").active, true);
|
||||
|
||||
@@ -102,7 +102,8 @@ assert.equal(Number.isFinite(homePose.z), true);
|
||||
store.dispatch({ type: "JOG", axis: "x", direction: 1, increment: 0.5 });
|
||||
await waitForTaskHal(store);
|
||||
state = store.getState();
|
||||
assert.equal(state.taskHalStatus.motionStatus.motion.teleopMode, 1);
|
||||
assert.equal(state.taskHalStatus.emcStatus.motion.valid, true);
|
||||
assert.equal(state.taskHalStatus.ui.taskMode, "manual");
|
||||
assert.equal(state.programRuntimeFeedback.sourceMode, "linuxcnc-task-motion-hal-wasm");
|
||||
assertNear(state.axisPose.x, homePose.x + 0.5, "task/HAL JOG X+ should keep HOME work-pose continuity");
|
||||
assertNear(state.axisPose.y, homePose.y, "task/HAL JOG X+ should not reset Y");
|
||||
@@ -135,7 +136,8 @@ await waitForTaskHal(store);
|
||||
state = store.getState();
|
||||
assert.equal(state.runState, "stopped");
|
||||
assert.equal(state.machine.interpState, "idle");
|
||||
assert.equal(state.taskHalStatus.motionStatus.motion.aborted, true);
|
||||
assert.equal(state.taskHalStatus.emcStatus.motion.valid, true);
|
||||
assert.equal(state.taskHalStatus.ui.interpState, "idle");
|
||||
|
||||
console.log("linuxcnc_task_hal_runtime_smoke=ok");
|
||||
console.log("task_hal_machine_file_smoke=ok");
|
||||
|
||||
@@ -49,7 +49,7 @@ assert.equal(staged.save.summary.kinds.remap >= 3, true);
|
||||
assert.equal(staged.save.gcodeSources.some((source) => source.filename === "impeller-7bl-xyzac.ngc"), true);
|
||||
assert.equal(staged.save.gcodeSources.some((source) => source.filename === "boat-xyzac.ngc"), true);
|
||||
assert.equal(staged.save.gcodeFiles.some((file) => file.sourceRel.endsWith("remap_subs/430remap.ngc")), true);
|
||||
assert.equal(staged.save.files.every((file) => file.opfsPath.startsWith("web-rtcp-5axis-sim-plan/machines/xyzac-trt/")), true);
|
||||
assert.equal(staged.save.files.every((file) => file.opfsPath.startsWith("web-rtcp-5axis-xyzbc-trt-sim-plan/machines/xyzac-trt/")), true);
|
||||
assert.throws(
|
||||
() => selectMachineFileProgram(staged.plan, staged.save, "configs/sim/axis/vismach/5axis/table-rotary-tilting/remap_subs/428remap.ngc"),
|
||||
/must come from LinuxCNC source demos/,
|
||||
@@ -59,6 +59,7 @@ const iniOpfsPath = staged.save.files.find((file) => file.sourceRel.endsWith("xy
|
||||
assert.equal(storage.files.get(iniOpfsPath).includes("KINEMATICS = xyzac-trt-kins"), true);
|
||||
|
||||
const store = createSimulationStore();
|
||||
store.dispatch({ type: "SET_PROFILE", profileId: "xyzac-trt" });
|
||||
store.dispatch({
|
||||
type: "ATTACH_INTERPRETER_RUNTIME",
|
||||
runtime: await createLinuxCncInterpreterRuntime(),
|
||||
|
||||
@@ -502,20 +502,33 @@ console.log("rtcp_store_smoke=ok");
|
||||
function taskHalStatusForSwitchkinsLine({ activeLine, switchkinsType }) {
|
||||
return {
|
||||
semanticBoundary: "linuxcnc_task_motion_hal_wasm_simulation_runtime",
|
||||
task: {
|
||||
state: "ON",
|
||||
mode: "AUTO",
|
||||
interpState: "READING",
|
||||
execState: "WAITING_FOR_MOTION",
|
||||
},
|
||||
motionStatus: {
|
||||
statusSource: "StandaloneEmcStatus",
|
||||
schemaVersion: 1,
|
||||
emcStatus: {
|
||||
source: "StandaloneEmcStatus",
|
||||
task: {
|
||||
state: "ON",
|
||||
mode: "AUTO",
|
||||
interpState: "READING",
|
||||
execState: "WAITING_FOR_MOTION",
|
||||
programOpen: true,
|
||||
openedLineCount: 999,
|
||||
openedSourceLineCount: 999,
|
||||
nextProgramLine: activeLine,
|
||||
},
|
||||
motion: {
|
||||
valid: true,
|
||||
programLine: activeLine,
|
||||
motionType: 1,
|
||||
switchkinsType,
|
||||
currentVel: 1,
|
||||
requestedVel: 1,
|
||||
inPosition: false,
|
||||
axisByName: { x: 1, y: 2, z: 3, a: 10, b: 0, c: 20 },
|
||||
traj: {
|
||||
queue: 1,
|
||||
actualPosition: { x: 1, y: 2, z: 3, a: 10, b: 0, c: 20 },
|
||||
},
|
||||
},
|
||||
},
|
||||
ui: {
|
||||
|
||||
@@ -102,7 +102,7 @@ async function verifyRunFeedbackLoop({ profileId, sourceRel, stopAction }) {
|
||||
assert.equal(history.every((entry) => entry.sourceMode === "linuxcnc-task-motion-hal-wasm"), true);
|
||||
assert.equal(history.every((entry) => entry.semanticBoundary === "linuxcnc_task_motion_hal_wasm_simulation_runtime"), true);
|
||||
assert.equal(history.some((entry) => entry.sourceMode === "fixture-line-playback"), false);
|
||||
assert.equal(history.every((entry) => entry.activeLineSource === "motion-status"), true);
|
||||
assert.equal(history.every((entry) => entry.activeLineSource === "emc-status"), true);
|
||||
assert.equal(history.every((entry) => entry.activeLineHalSynced === true), true);
|
||||
assert.equal(history.every((entry) => entry.line === entry.motionProgramLine), true);
|
||||
assert.equal(history.every((entry) => entry.line === entry.halProgramLine), true);
|
||||
@@ -128,8 +128,8 @@ async function verifyRunFeedbackLoop({ profileId, sourceRel, stopAction }) {
|
||||
assert.equal(state.taskHalStatus.summary.halSyncReady, true);
|
||||
|
||||
const activeLineBeforePause = state.activeLine;
|
||||
const motionLineBeforePause = state.taskHalStatus.motionStatus.motion.programLine;
|
||||
const motionAxisBeforePause = { ...state.taskHalStatus.motionStatus.axis };
|
||||
const motionLineBeforePause = state.taskHalStatus.emcStatus.motion.programLine;
|
||||
const motionAxisBeforePause = { ...state.taskHalStatus.ui.axisPose };
|
||||
store.dispatch({ type: "PAUSE_RESUME" });
|
||||
await waitForTaskHalCommand(store);
|
||||
state = store.getState();
|
||||
@@ -137,24 +137,24 @@ async function verifyRunFeedbackLoop({ profileId, sourceRel, stopAction }) {
|
||||
assert.equal(state.machine.motionPaused, true);
|
||||
assert.equal(state.machine.taskPaused, true);
|
||||
assert.equal(state.taskHalStatus.ui.motionPaused, true);
|
||||
assert.equal(state.taskHalStatus.motionStatus.motion.paused, true);
|
||||
assert.equal(state.taskHalStatus.emcStatus.motion.paused, true);
|
||||
assert.equal(state.programRuntimeFeedback.paused, true);
|
||||
assert.equal(state.activeLine, activeLineBeforePause);
|
||||
assert.equal(state.taskHalStatus.motionStatus.motion.programLine, motionLineBeforePause);
|
||||
assert.deepEqual(state.taskHalStatus.motionStatus.axis, motionAxisBeforePause);
|
||||
assert.equal(state.taskHalStatus.emcStatus.motion.programLine, motionLineBeforePause);
|
||||
assert.deepEqual(state.taskHalStatus.ui.axisPose, motionAxisBeforePause);
|
||||
await new Promise((resolve) => setTimeout(resolve, 120));
|
||||
state = store.getState();
|
||||
assert.equal(state.runState, "paused");
|
||||
assert.equal(state.activeLine, activeLineBeforePause);
|
||||
assert.equal(state.taskHalStatus.motionStatus.motion.programLine, motionLineBeforePause);
|
||||
assert.deepEqual(state.taskHalStatus.motionStatus.axis, motionAxisBeforePause);
|
||||
assert.equal(state.taskHalStatus.emcStatus.motion.programLine, motionLineBeforePause);
|
||||
assert.deepEqual(state.taskHalStatus.ui.axisPose, motionAxisBeforePause);
|
||||
|
||||
store.dispatch({ type: "PAUSE_RESUME" });
|
||||
await waitForTaskHalCommand(store);
|
||||
await waitForState(store, (nextState) => nextState.machine.motionPaused === false && nextState.runState !== "paused");
|
||||
state = store.getState();
|
||||
assert.equal(state.machine.motionPaused, false);
|
||||
assert.equal(state.taskHalStatus.motionStatus.motion.paused, false);
|
||||
assert.equal(state.taskHalStatus.emcStatus.motion.paused, false);
|
||||
|
||||
const cycleAfterRun = state.taskHalStatus.ui.taskCycle;
|
||||
store.dispatch({ type: "STEP" });
|
||||
|
||||
@@ -119,6 +119,7 @@ assert.equal(fallbackSave.storageMode, "memory-fallback");
|
||||
assert.equal(fallbackSave.storageCapability.opfsUnavailable, true);
|
||||
|
||||
const store = createSimulationStore();
|
||||
store.dispatch({ type: "SET_PROFILE", profileId: "xyzac-trt" });
|
||||
const machineStorage = createMemorySessionStorage();
|
||||
await store.stageMachineFiles({ storage: machineStorage });
|
||||
assert.equal(store.getState().toolDbReadiness.toolDbProcessReady, true);
|
||||
|
||||
@@ -12,6 +12,7 @@ import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc
|
||||
import {
|
||||
buildTaskHalProgramMotionPlan,
|
||||
buildTaskHalSessionFromMachineFiles,
|
||||
normalizeTaskHalStatus,
|
||||
wrapTaskHalSdk,
|
||||
} from "../../app/src/runtime/linuxcnc-task-hal-runtime.js";
|
||||
import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
|
||||
@@ -199,9 +200,116 @@ const taskHalStatus = taskHal.readStatus();
|
||||
assert.equal(taskHalStatus.summary.taskRuntimeReady, true);
|
||||
assert.equal(taskHalStatus.summary.halSyncReady, true);
|
||||
assert.equal(taskHalStatus.ui.activeLine >= 1, true);
|
||||
assert.equal(Number.isFinite(taskHalStatus.motionStatus.axis.x), true);
|
||||
assert.equal(Number.isFinite(taskHalStatus.motionStatus.axis.y), true);
|
||||
assert.equal(Number.isFinite(taskHalStatus.motionStatus.axis.z), true);
|
||||
assert.equal(taskHalStatus.statusSource, "StandaloneEmcStatus");
|
||||
assert.equal(taskHalStatus.schemaVersion, 1);
|
||||
assert.equal(taskHalStatus.emcStatus.source, "StandaloneEmcStatus");
|
||||
assert.equal("task" in taskHalStatus, false);
|
||||
assert.equal("motionStatus" in taskHalStatus, false);
|
||||
assert.equal(taskHalStatus.ui.activeLine, taskHalStatus.emcStatus.task.currentLine);
|
||||
assert.equal(taskHalStatus.ui.motionProgramLine, taskHalStatus.emcStatus.motion.programLine);
|
||||
assert.equal(taskHalStatus.ui.motionQueueDepth, taskHalStatus.emcStatus.motion.traj.queue);
|
||||
assert.equal(taskHalStatus.ui.axisPose.x, taskHalStatus.emcStatus.motion.axisByName.x);
|
||||
assert.equal(Number.isFinite(taskHalStatus.ui.axisPose.x), true);
|
||||
assert.equal(Number.isFinite(taskHalStatus.ui.axisPose.y), true);
|
||||
assert.equal(Number.isFinite(taskHalStatus.ui.axisPose.z), true);
|
||||
|
||||
const emcOnlyTaskHalStatus = normalizeTaskHalStatus({
|
||||
statusSource: "StandaloneEmcStatus",
|
||||
schemaVersion: 1,
|
||||
emcStatus: {
|
||||
source: "StandaloneEmcStatus",
|
||||
task: {
|
||||
status: "EXEC",
|
||||
state: "ON",
|
||||
mode: "AUTO",
|
||||
interpState: "READING",
|
||||
execState: "EXEC",
|
||||
cycle: 17,
|
||||
programOpen: true,
|
||||
planId: 9,
|
||||
motionPlanLoaded: true,
|
||||
allHomed: true,
|
||||
currentLine: 12,
|
||||
readLine: 12,
|
||||
motionLine: 12,
|
||||
callLevel: 1,
|
||||
},
|
||||
motion: {
|
||||
valid: true,
|
||||
status: "EXEC",
|
||||
programLine: 12,
|
||||
motionId: 44,
|
||||
enabled: true,
|
||||
commandQueueDepth: 2,
|
||||
activeDepth: 1,
|
||||
queueFull: false,
|
||||
joint0: {
|
||||
motorPosCmd: 10,
|
||||
motorPosFb: 9.5,
|
||||
},
|
||||
traj: {
|
||||
enabled: true,
|
||||
inpos: false,
|
||||
queue: 2,
|
||||
activeQueue: 1,
|
||||
queueFull: false,
|
||||
id: 44,
|
||||
paused: false,
|
||||
singleStepping: false,
|
||||
actualPosition: {
|
||||
x: 10,
|
||||
y: 20,
|
||||
z: 30,
|
||||
b: 40,
|
||||
c: 50,
|
||||
},
|
||||
currentVel: 1.25,
|
||||
},
|
||||
axisByName: {
|
||||
x: 10,
|
||||
y: 20,
|
||||
z: 30,
|
||||
b: 40,
|
||||
c: 50,
|
||||
},
|
||||
},
|
||||
},
|
||||
halSnapshot: {
|
||||
ready: true,
|
||||
pins: {
|
||||
"motion.program-line": { value: 12 },
|
||||
"motion.switchkins-type": { value: 1 },
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.equal(emcOnlyTaskHalStatus.summary.taskRuntimeReady, true);
|
||||
assert.equal(emcOnlyTaskHalStatus.summary.motionRuntimeReady, true);
|
||||
assert.equal(emcOnlyTaskHalStatus.summary.halSyncReady, true);
|
||||
assert.equal(emcOnlyTaskHalStatus.ui.taskMode, "auto");
|
||||
assert.equal(emcOnlyTaskHalStatus.ui.interpState, "reading");
|
||||
assert.equal(emcOnlyTaskHalStatus.ui.taskCycle, 17);
|
||||
assert.equal(emcOnlyTaskHalStatus.ui.activeLine, 12);
|
||||
assert.equal(emcOnlyTaskHalStatus.ui.motionQueueDepth, 2);
|
||||
assert.equal(emcOnlyTaskHalStatus.ui.motionId, 44);
|
||||
assert.equal(emcOnlyTaskHalStatus.ui.currentVelocity, 75);
|
||||
assert.deepEqual(emcOnlyTaskHalStatus.ui.axisPose, {
|
||||
x: 10,
|
||||
y: 20,
|
||||
z: 30,
|
||||
a: 0,
|
||||
b: 40,
|
||||
c: 50,
|
||||
});
|
||||
assert.equal("task" in emcOnlyTaskHalStatus, false);
|
||||
assert.equal("motionStatus" in emcOnlyTaskHalStatus, false);
|
||||
assert.equal(emcOnlyTaskHalStatus.emcStatus.motion.joint0.motorPosFb, 9.5);
|
||||
assert.throws(
|
||||
() => normalizeTaskHalStatus({
|
||||
task: { state: "ON", mode: "AUTO" },
|
||||
motionStatus: { motion: { programLine: 1 }, axis: { x: 1 } },
|
||||
}),
|
||||
/removed legacy fields/,
|
||||
);
|
||||
|
||||
const store = createSimulationStore();
|
||||
const state = store.getState();
|
||||
@@ -480,31 +588,65 @@ staleManualRunningStore.dispatch({
|
||||
type: "TASK_HAL_STATUS_APPLIED",
|
||||
operatorMessage: "simulated stale running task/HAL status",
|
||||
status: {
|
||||
taskRuntimeReady: true,
|
||||
taskCommandsDriveMotionRuntime: true,
|
||||
task: {
|
||||
state: "ON",
|
||||
mode: "AUTO",
|
||||
interpState: "READING",
|
||||
execState: "EXEC",
|
||||
taskCycle: 123,
|
||||
},
|
||||
motionStatus: {
|
||||
cycle: 25,
|
||||
motionHalSyncReady: true,
|
||||
statusSource: "StandaloneEmcStatus",
|
||||
schemaVersion: 1,
|
||||
emcStatus: {
|
||||
source: "StandaloneEmcStatus",
|
||||
task: {
|
||||
state: "ON",
|
||||
mode: "AUTO",
|
||||
interpState: "READING",
|
||||
execState: "EXEC",
|
||||
cycle: 123,
|
||||
taskCycle: 123,
|
||||
programOpen: true,
|
||||
openedLineCount: 999,
|
||||
openedSourceLineCount: 999,
|
||||
nextProgramLine: Number(lockedPauseState.activeLine || 1) + 5,
|
||||
},
|
||||
motion: {
|
||||
valid: true,
|
||||
programLine: Number(lockedPauseState.activeLine || 1) + 5,
|
||||
currentVel: 12,
|
||||
requestedVel: 12,
|
||||
inPosition: false,
|
||||
axisByName: {
|
||||
x: 99,
|
||||
y: 88,
|
||||
z: 77,
|
||||
b: 66,
|
||||
c: 55,
|
||||
},
|
||||
traj: {
|
||||
queue: 1,
|
||||
actualPosition: {
|
||||
x: 99,
|
||||
y: 88,
|
||||
z: 77,
|
||||
b: 66,
|
||||
c: 55,
|
||||
},
|
||||
},
|
||||
},
|
||||
axis: {
|
||||
},
|
||||
ui: {
|
||||
taskState: "on",
|
||||
taskMode: "auto",
|
||||
interpState: "reading",
|
||||
execState: "exec",
|
||||
activeLine: Number(lockedPauseState.activeLine || 1) + 5,
|
||||
motionProgramLine: Number(lockedPauseState.activeLine || 1) + 5,
|
||||
activeLineSource: "emc-status",
|
||||
taskCycle: 123,
|
||||
servoCycle: 25,
|
||||
axisPose: {
|
||||
x: 99,
|
||||
y: 88,
|
||||
z: 77,
|
||||
b: 66,
|
||||
c: 55,
|
||||
},
|
||||
currentVelocity: 720,
|
||||
},
|
||||
halSnapshot: {
|
||||
ready: true,
|
||||
@@ -515,7 +657,7 @@ staleManualRunningStore.dispatch({
|
||||
},
|
||||
});
|
||||
assert.equal(staleManualRunningStore.getState().runState, "running");
|
||||
assert.equal(staleManualRunningStore.getState().machine.mode, "manual");
|
||||
assert.equal(staleManualRunningStore.getState().machine.mode, "auto");
|
||||
assert.equal(staleManualRunningStore.getState().machine.interpState, "reading");
|
||||
assert.equal(staleManualRunningStore.getState().taskHalPauseLock, null);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user