Use native LinuxCNC homing and consolidate web panel status
This commit is contained in:
@@ -13,7 +13,7 @@ const [contract, app, panel, preview] = await Promise.all([
|
||||
const controlIds = [
|
||||
"menu-file", "menu-file-edit-tool-data", "menu-file-quit", "menu-help", "menu-help-about", "about-close",
|
||||
"toolbar-estop", "toolbar-power", "toolbar-open", "toolbar-load-case", "toolbar-reload", "toolbar-run", "toolbar-step", "toolbar-pause-resume", "toolbar-stop", "toolbar-block-delete", "toolbar-optional-pause", "toolbar-zoom-in", "toolbar-zoom-out", "toolbar-view-z", "toolbar-view-z2", "toolbar-view-x", "toolbar-view-y", "toolbar-view-y2", "toolbar-view-p", "toolbar-rotate", "toolbar-clear-live-plot",
|
||||
"workspace-case-select", "workspace-load-3d-chips", "manual-home-all",
|
||||
"workspace-case-select", "workspace-load-3d-chips", "manual-home-all", "manual-home-joint-select",
|
||||
"switchkins-0", "switchkins-1", "switchkins-2",
|
||||
"tab-manual", "tab-mdi", "tab-diagnostics", "tab-preview", "tab-dro",
|
||||
"manual-axis-x", "manual-axis-y", "manual-axis-z", "manual-axis-a", "manual-axis-c",
|
||||
|
||||
@@ -1,79 +1,107 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const modulePath = process.env.PLAYWRIGHT_MODULE;
|
||||
assert.ok(modulePath, "PLAYWRIGHT_MODULE is required");
|
||||
const imported = await import(pathToFileURL(modulePath).href);
|
||||
const playwright = imported.default ?? imported;
|
||||
const browser = await playwright.chromium.launch({
|
||||
headless: false,
|
||||
executablePath: process.env.BROWSER_EXECUTABLE_PATH ?? "/usr/bin/google-chrome",
|
||||
args: ["--disable-gpu", "--ignore-certificate-errors", "--window-size=1440,1000"],
|
||||
});
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
|
||||
const main = page.locator("main.axis-app");
|
||||
page.on("dialog", async (dialog) => { if (dialog.type() === "confirm") await dialog.accept(); else await dialog.dismiss(); });
|
||||
const state = () => main.evaluate((element) => {
|
||||
const preview = element.querySelector("[data-xyzac-preview]");
|
||||
return {
|
||||
loading: element.dataset.axisProgramLoading,
|
||||
homed: element.dataset.axisHomed,
|
||||
pose: preview?.dataset.xyzacLivePose,
|
||||
hud: element.querySelector(".axis-preview-hud")?.textContent ?? "",
|
||||
queue: element.dataset.axisTpQueueDepth,
|
||||
command: element.querySelector(".command-status")?.textContent ?? "",
|
||||
error: element.querySelector(".diagnostic-error")?.textContent ?? null,
|
||||
const imported = await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE).href);
|
||||
const { chromium } = imported.default ?? imported;
|
||||
const url = process.env.XYZAC_PANEL_URL ?? "http://127.0.0.1:5175/";
|
||||
const browser = await chromium.launch({ headless: false, slowMo: 100, executablePath: "/usr/bin/google-chrome", args: ["--disable-gpu"] });
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 1100 } });
|
||||
const errors = [];
|
||||
let confirmHome = true;
|
||||
page.on("pageerror", (e) => errors.push(e.message));
|
||||
page.on("dialog", async (d) => { await page.waitForTimeout(600); if (d.type() === "confirm" && confirmHome) await d.accept(); else await d.dismiss(); });
|
||||
await page.addInitScript(() => {
|
||||
window.homeResults = []; window.homeRequests = [];
|
||||
const Original = window.Worker;
|
||||
window.Worker = class extends Original {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.addEventListener("message", ({ data }) => {
|
||||
const p = data.payload;
|
||||
window.homeResults.push({ seq: data.seq, kind: data.kind, command: p?.command, home: p?.homing,
|
||||
status: p?.source_status ?? p?.panel_snapshot?.source_status,
|
||||
pose: p?.panel_snapshot?.visual?.pose, epoch: p?.panel_snapshot?.visual?.live_path_epoch });
|
||||
if (window.homeResults.length > 500) window.homeResults.shift();
|
||||
});
|
||||
}
|
||||
postMessage(data, ...args) { window.homeRequests.push({ seq: data.seq, kind: data.kind, payload: data.payload }); super.postMessage(data, ...args); }
|
||||
};
|
||||
});
|
||||
const wait = async (predicate, label) => {
|
||||
const started = Date.now(); let latest;
|
||||
while (Date.now() - started < 45000) { latest = await state(); if (predicate(latest)) return latest; await page.waitForTimeout(100); }
|
||||
throw new Error(`${label}:${JSON.stringify(latest)}`);
|
||||
const control = (id) => page.locator(`[data-axis-control="${id}"]`);
|
||||
const status = () => page.evaluate(() => window.homeResults.filter((r) => r.status).at(-1).status);
|
||||
const home = async (id) => {
|
||||
const seq = await page.evaluate(() => window.homeRequests.at(-1)?.seq ?? 0);
|
||||
await control(id).click();
|
||||
await page.waitForFunction((seq) => window.homeResults.some((r) => r.seq > seq && r.home?.phase === "accepted"), seq);
|
||||
await page.waitForFunction((seq) => window.homeResults.some((r) => r.seq > seq && r.status?.homing_history?.length > 1 && !r.status.homing_active), seq);
|
||||
const result = await status();
|
||||
assert.equal(result.task.mode, "MANUAL"); assert.equal(result.task.interpState, "INTERP_IDLE"); assert.equal(result.traj.mode, result.joint.every((j) => j.homed) ? "TELEOP" : "FREE");
|
||||
assert.ok(result.homing_history.some((event) => event.joint.some((j) => j.home_state === 21 && j.homing)));
|
||||
return result;
|
||||
};
|
||||
const jog = async (axis) => {
|
||||
await control(`manual-axis-${axis}`).check();
|
||||
const button = control("manual-jog-positive");
|
||||
await button.scrollIntoViewIfNeeded();
|
||||
const box = await button.boundingBox();
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
await page.mouse.down(); await page.waitForTimeout(900); await page.mouse.up();
|
||||
await page.waitForFunction(() => document.querySelector("main").dataset.axisTpQueueDepth === "0");
|
||||
await page.waitForTimeout(250);
|
||||
return status();
|
||||
};
|
||||
const control = (id) => main.locator(`[data-axis-control="${id}"]`);
|
||||
const click = async (id) => { const button = control(id); await wait(async () => await button.count() > 0 && !await button.isDisabled(), `${id}_enabled`); await button.click(); };
|
||||
const pose = async () => JSON.parse((await state()).pose ?? "{}");
|
||||
const screenshot = async (name) => { await page.screenshot({ path: `/tmp/axis-home-reset-${name}.png` }); console.log(name, JSON.stringify(await state())); };
|
||||
|
||||
try {
|
||||
await page.goto(process.env.XYZAC_PANEL_URL ?? "http://127.0.0.1:5175/", { waitUntil: "domcontentloaded" });
|
||||
await wait((value) => value.loading === "false", "startup");
|
||||
await click("toolbar-power"); await wait((value) => value.command.includes("POWER acknowledged"), "power");
|
||||
await click("manual-home-all"); await wait((value) => value.command.includes("HOME acknowledged") && value.homed === "[true,true,true,true,true]", "home_all");
|
||||
await screenshot("01-home-all-zero");
|
||||
await control("manual-axis-x").check();
|
||||
const jog = control("manual-jog-positive");
|
||||
await jog.dispatchEvent("pointerdown", { pointerId: 41, button: 0 });
|
||||
await wait((value) => value.command.includes("JOG_START acknowledged"), "jog_start");
|
||||
await page.waitForTimeout(450);
|
||||
await jog.dispatchEvent("pointerup", { pointerId: 41, button: 0 });
|
||||
await wait((value) => value.command.includes("JOG_STOP acknowledged") && value.queue === "0", "jog_stop");
|
||||
const jogged = await pose(); assert.ok(Math.abs(Number(jogged.x)) > 1e-6, JSON.stringify(jogged));
|
||||
await screenshot("02-jogged-x");
|
||||
await click("manual-home-axis");
|
||||
const movingAxis = await wait((value) => value.command.includes("HOME acknowledged") && value.queue !== "0", "home_axis_motion");
|
||||
const movingAxisPose = JSON.parse(movingAxis.pose); assert.ok(Math.abs(Number(movingAxisPose.x)) > 1e-6, JSON.stringify(movingAxisPose));
|
||||
await screenshot("03-home-axis-moving");
|
||||
const homedAxis = await wait((value) => value.command.includes("HOME acknowledged") && value.queue === "0", "home_axis");
|
||||
const homeAxisPose = JSON.parse(homedAxis.pose); assert.ok(Math.abs(Number(homeAxisPose.x)) < 1e-8, JSON.stringify(homeAxisPose));
|
||||
await screenshot("04-home-axis-zero");
|
||||
for (const [axis, pointerId] of [["y", 42], ["z", 43], ["a", 44], ["c", 45]]) {
|
||||
await control(`manual-axis-${axis}`).check();
|
||||
const button = control("manual-jog-negative");
|
||||
await button.dispatchEvent("pointerdown", { pointerId, button: 0 });
|
||||
await wait((value) => value.command.includes("JOG_START acknowledged"), `${axis}_jog_start`);
|
||||
await page.waitForTimeout(300);
|
||||
await button.dispatchEvent("pointerup", { pointerId, button: 0 });
|
||||
await wait((value) => value.command.includes("JOG_STOP acknowledged") && value.queue === "0", `${axis}_jog_stop`);
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => document.querySelector("main")?.dataset.axisProgramLoading === "false");
|
||||
await control("toolbar-power").click();
|
||||
await home("manual-home-all");
|
||||
for (const [joint, axis] of ["x", "y", "z", "a", "c"].entries()) {
|
||||
process.stderr.write(`Visible UI: Jog ${axis.toUpperCase()}, then Home Joint ${joint}\n`);
|
||||
const before = await jog(axis);
|
||||
assert.ok(before.joint[joint].position > 0);
|
||||
await control("manual-home-joint-select").selectOption(String(joint));
|
||||
const after = await home("manual-home-axis");
|
||||
for (let j = 0; j < 5; j++) {
|
||||
assert.ok(Math.abs(after.joint[j].motor_position - before.joint[j].motor_position) < 1e-8);
|
||||
assert.ok(Math.abs(after.joint[j].position - (j === joint ? 0 : before.joint[j].position)) < 1e-8);
|
||||
}
|
||||
}
|
||||
await screenshot("05-multi-axis-jogged");
|
||||
await click("manual-home-all");
|
||||
const movingAll = await wait((value) => value.command.includes("HOME acknowledged") && value.queue !== "0", "home_all_motion");
|
||||
const movingAllPose = JSON.parse(movingAll.pose); assert.ok(Object.entries(movingAllPose).some(([axis, value]) => ["x", "y", "z", "a", "c"].includes(axis) && Math.abs(Number(value)) > 1e-6), JSON.stringify(movingAllPose));
|
||||
await screenshot("06-home-all-moving");
|
||||
const final = await wait((value) => value.command.includes("HOME acknowledged") && value.queue === "0", "home_all_again");
|
||||
const finalPose = JSON.parse(final.pose);
|
||||
for (const axis of ["x", "y", "z", "a", "c"]) assert.ok(Math.abs(Number(finalPose[axis])) < 1e-8, `${axis}:${JSON.stringify(finalPose)}`);
|
||||
await screenshot("07-home-all-zero");
|
||||
console.log(JSON.stringify({ schema: "xyzac-home-reset-browser.v1", status: "verified", url: process.env.XYZAC_PANEL_URL ?? "http://127.0.0.1:5175/", jogged, home_axis: homeAxisPose, home_all: finalPose, screenshots: ["/tmp/axis-home-reset-01-home-all-zero.png", "/tmp/axis-home-reset-02-jogged-x.png", "/tmp/axis-home-reset-03-home-axis-moving.png", "/tmp/axis-home-reset-04-home-axis-zero.png", "/tmp/axis-home-reset-05-multi-axis-jogged.png", "/tmp/axis-home-reset-06-home-all-moving.png", "/tmp/axis-home-reset-07-home-all-zero.png"] }));
|
||||
const before = await jog("x");
|
||||
confirmHome = false;
|
||||
const count = await page.evaluate(() => window.homeRequests.filter((r) => r.kind === "HOME").length);
|
||||
await control("manual-home-all").click(); await page.waitForTimeout(800);
|
||||
assert.equal(await page.evaluate(() => window.homeRequests.filter((r) => r.kind === "HOME").length), count);
|
||||
confirmHome = true;
|
||||
const after = await home("manual-home-all");
|
||||
assert.deepEqual(after.joint.map((j) => j.motor_position), before.joint.map((j) => j.motor_position));
|
||||
assert.ok(after.joint.every((j) => j.homed && Math.abs(j.position) < 1e-8));
|
||||
await control("tab-manual").click();
|
||||
await page.waitForFunction(() => window.homeResults.filter((r) => r.status).at(-1)?.status.traj.mode === "TELEOP");
|
||||
await page.locator('[aria-label="LinuxCNC status"]').scrollIntoViewIfNeeded();
|
||||
await page.screenshot({ path: "/tmp/axis-native-home-desktop.png", fullPage: true });
|
||||
const pixelCheck = async () => page.locator("canvas[data-engine]").evaluate((canvas) => {
|
||||
const gl = canvas.getContext("webgl2") ?? canvas.getContext("webgl");
|
||||
const data = new Uint8Array(canvas.width * canvas.height * 4);
|
||||
gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
||||
const colors = new Set(); for (let i=0;i<data.length;i+=16) colors.add(`${data[i]},${data[i+1]},${data[i+2]}`);
|
||||
return colors.size;
|
||||
});
|
||||
assert.ok(await pixelCheck() > 5);
|
||||
await control("toolbar-run").click();
|
||||
await page.waitForFunction(() => window.homeResults.filter((r) => r.status).at(-1)?.status.task.mode === "AUTO");
|
||||
assert.equal((await status()).traj.mode, "COORD");
|
||||
await control("toolbar-pause-resume").click();
|
||||
await page.waitForFunction(() => window.homeResults.filter((r) => r.status).at(-1)?.status.task.interpState === "INTERP_PAUSED");
|
||||
await control("tab-manual").click(); assert.equal((await status()).task.mode, "AUTO");
|
||||
assert.equal(await control("manual-home-all").isDisabled(), true);
|
||||
await control("toolbar-stop").click();
|
||||
await page.waitForFunction(() => document.querySelector("main").dataset.axisTpQueueDepth === "0");
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await home("manual-home-all");
|
||||
await page.locator('[aria-label="LinuxCNC status"]').scrollIntoViewIfNeeded();
|
||||
await page.screenshot({ path: "/tmp/axis-native-home-mobile.png", fullPage: true });
|
||||
assert.ok(await pixelCheck() > 5);
|
||||
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
|
||||
assert.deepEqual(errors, []);
|
||||
console.log(JSON.stringify({ status: "verified", url, checks: ["visible-five-joint-home", "motor-position-preserved", "native-state-history", "cancel", "auto-paused-mode", "desktop-mobile-canvas"] }));
|
||||
} finally { await browser.close(); }
|
||||
|
||||
@@ -22,8 +22,8 @@ const state = () => main.evaluate((element) => {
|
||||
loading: element.dataset.axisProgramLoading,
|
||||
path: element.dataset.axisProgramPath,
|
||||
program: element.dataset.axisProgramStatus,
|
||||
operation: element.querySelector(".operation-state")?.textContent?.trim(),
|
||||
machine: element.querySelector(".machine-state")?.textContent?.trim(),
|
||||
operation: `Operation ${element.dataset.axisStateOperation}`,
|
||||
machine: `Machine ${element.dataset.axisStateMachine}`,
|
||||
homed: element.dataset.axisHomed,
|
||||
line: element.dataset.axisCurrentLine,
|
||||
queue: element.dataset.axisTpQueueDepth,
|
||||
|
||||
@@ -11,7 +11,7 @@ const errors = [];
|
||||
page.on("pageerror", (error) => errors.push(`pageerror:${error.message}`));
|
||||
page.on("console", (message) => { if (message.type() === "error") errors.push(`console:${message.text()}`); });
|
||||
const main = page.locator("main.axis-app");
|
||||
const state = () => main.evaluate((element) => ({ source: element.dataset.axisStateSource, machine: element.querySelector(".machine-state")?.textContent?.trim(), program: element.dataset.axisProgramStatus, line: element.dataset.axisCurrentLine, path: element.querySelector(".xyzac-tool-preview")?.dataset.xyzacToolpathCount, live: element.querySelector(".xyzac-tool-preview")?.dataset.xyzacLiveTraceCount, loading: element.querySelector(".case-status")?.getAttribute("aria-busy") === "true", command: element.querySelector(".command-status")?.textContent, error: element.querySelector(".diagnostic-error")?.textContent ?? null }));
|
||||
const state = () => main.evaluate((element) => ({ source: element.dataset.axisStateSource, machine: `Machine ${element.dataset.axisStateMachine}`, program: element.dataset.axisProgramStatus, line: element.dataset.axisCurrentLine, path: element.querySelector(".xyzac-tool-preview")?.dataset.xyzacToolpathCount, live: element.querySelector(".xyzac-tool-preview")?.dataset.xyzacLiveTraceCount, loading: element.querySelector(".case-status")?.getAttribute("aria-busy") === "true", command: element.querySelector(".command-status")?.textContent, error: element.querySelector(".diagnostic-error")?.textContent ?? null }));
|
||||
const wait = async (predicate, label, timeout = 45000) => { const started = Date.now(); let last; while (Date.now() - started < timeout) { last = await state(); if (predicate(last)) return last; await page.waitForTimeout(100); } throw new Error(`${label}:${JSON.stringify(last)}`); };
|
||||
const click = async (locator, label) => { for (let index = 0; index < 300; index += 1) { if (await locator.count() && !await locator.first().isDisabled()) { await locator.first().click(); return; } await page.waitForTimeout(100); } throw new Error(`${label}:disabled_or_missing`); };
|
||||
const loadFile = async (name, source) => { const input = main.locator('input[type="file"][aria-label="Open G-code file"]'); await input.setInputFiles({ name, mimeType: "text/plain", buffer: Buffer.from(source) }); return wait((value) => value.program === "loaded" && !value.loading && value.error === null, `${name}_loaded`); };
|
||||
|
||||
@@ -22,8 +22,8 @@ page.on("dialog", async (dialog) => { dialogs.push({ type: dialog.type(), messag
|
||||
const main = page.locator("main.axis-app");
|
||||
const state = () => main.evaluate((element) => ({
|
||||
source: element.dataset.axisStateSource,
|
||||
machine: element.querySelector(".machine-state")?.textContent?.trim(),
|
||||
operation: element.querySelector(".operation-state")?.textContent?.trim(),
|
||||
machine: `Machine ${element.dataset.axisStateMachine}`,
|
||||
operation: `Operation ${element.dataset.axisStateOperation}`,
|
||||
program: element.dataset.axisProgramStatus,
|
||||
line: element.dataset.axisCurrentLine,
|
||||
queue: element.dataset.axisTpQueueDepth,
|
||||
|
||||
@@ -9,7 +9,7 @@ if (!modulePath) {
|
||||
|
||||
const imported = await import(pathToFileURL(modulePath).href);
|
||||
const playwright = imported.default ?? imported;
|
||||
const browser = await playwright.chromium.launch({ headless: true, executablePath: process.env.BROWSER_EXECUTABLE_PATH ?? "/usr/bin/google-chrome", args: ["--ignore-certificate-errors", "--disable-gpu"] });
|
||||
const browser = await playwright.chromium.launch({ headless: process.env.HEADED !== "1", executablePath: process.env.BROWSER_EXECUTABLE_PATH ?? "/usr/bin/google-chrome", args: ["--ignore-certificate-errors", "--disable-gpu"] });
|
||||
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 }, ignoreHTTPSErrors: true });
|
||||
const failures = [];
|
||||
const browserErrors = [];
|
||||
@@ -29,8 +29,8 @@ const state = () => main.evaluate((element) => ({
|
||||
source: element.dataset.axisStateSource,
|
||||
motion: element.dataset.axisMotionAuthority,
|
||||
task: element.dataset.axisTaskAuthority,
|
||||
machine: element.querySelector(".machine-state")?.textContent?.trim(),
|
||||
operation: element.querySelector(".operation-state")?.textContent?.trim(),
|
||||
machine: `Machine ${element.dataset.axisStateMachine}`,
|
||||
operation: `Operation ${element.dataset.axisStateOperation}`,
|
||||
case: element.querySelector(".case-status")?.textContent?.trim(),
|
||||
mode: element.dataset.axisTaskMode,
|
||||
program: element.dataset.axisProgramStatus,
|
||||
@@ -115,7 +115,7 @@ try {
|
||||
results.push(await record("MANUAL MODE", async () => { await main.getByRole("tab", { name: "Manual Control [F3]" }).click(); await wait((s) => s.mode === "MANUAL" || s.command?.includes("SET_MODE acknowledged"), "manual_mode_ack"); }));
|
||||
results.push(await record("HOME/TOUCH OFF boundaries", async () => {
|
||||
await click(main.locator('button[data-axis-control="manual-home-axis"]'), "home_axis_repeat");
|
||||
await wait((s) => s.error === null && s.command?.includes("HOME acknowledged"), "home_axis_ack");
|
||||
await wait((s) => s.error === null && s.command?.includes("HOME complete"), "home_joint_complete");
|
||||
await click(main.locator('button[data-axis-control="manual-touch-off"]'), "touch_off");
|
||||
await wait((s) => s.error === null && s.command?.includes("TOUCH_OFF acknowledged"), "touch_off_ack");
|
||||
if (!await main.locator('[data-axis-control="manual-tool-touch-off"]').isDisabled()) throw new Error("tool_touch_off_must_be_disabled");
|
||||
|
||||
@@ -26,7 +26,7 @@ const control = (id) => page.locator(`[data-axis-control="${id}"]`);
|
||||
const state = () => page.locator("main.axis-app").evaluate((main) => {
|
||||
const viewer = main.querySelector("[data-xyzac-preview]");
|
||||
return { loading: main.dataset.axisProgramLoading, program: main.dataset.axisProgramStatus, path: main.dataset.axisProgramPath,
|
||||
mode: main.dataset.axisTaskMode, operation: main.querySelector("[data-axis-state-operation]")?.dataset.axisStateOperation,
|
||||
mode: main.dataset.axisTaskMode, operation: main.dataset.axisStateOperation,
|
||||
pose: viewer?.dataset.xyzacLivePose, tick: Number(viewer?.dataset.xyzacTick), epoch: viewer?.dataset.xyzacLivePathEpoch,
|
||||
queue: Number(main.dataset.axisTpQueueDepth), command: main.querySelector(".command-status")?.textContent,
|
||||
error: main.querySelector(".diagnostic-error")?.textContent };
|
||||
|
||||
@@ -27,8 +27,8 @@ const state = () => main.evaluate((element) => ({
|
||||
source: element.dataset.axisStateSource,
|
||||
authority: element.dataset.axisAuthority,
|
||||
motion: element.dataset.axisMotionAuthority,
|
||||
machine: element.querySelector(".machine-state")?.textContent?.trim(),
|
||||
operation: element.querySelector(".operation-state")?.textContent?.trim(),
|
||||
machine: `Machine ${element.dataset.axisStateMachine}`,
|
||||
operation: `Operation ${element.dataset.axisStateOperation}`,
|
||||
mode: element.dataset.axisTaskMode,
|
||||
program: element.dataset.axisProgramStatus,
|
||||
runEnabled: element.dataset.axisRunEnabled,
|
||||
|
||||
@@ -31,8 +31,8 @@ const state = () => main.evaluate((element) => ({
|
||||
sourceHash: element.dataset.axisProgramSourceHash,
|
||||
program: element.dataset.axisProgramStatus,
|
||||
runEnabled: element.dataset.axisRunEnabled,
|
||||
machine: element.querySelector(".machine-state")?.textContent?.trim(),
|
||||
operation: element.querySelector(".operation-state")?.textContent?.trim(),
|
||||
machine: `Machine ${element.dataset.axisStateMachine}`,
|
||||
operation: `Operation ${element.dataset.axisStateOperation}`,
|
||||
homed: element.dataset.axisHomed,
|
||||
line: element.dataset.axisCurrentLine,
|
||||
queue: element.dataset.axisTpQueueDepth,
|
||||
|
||||
Reference in New Issue
Block a user