// 最小演示:打开本地发布站点 → 上电 → HOME ALL → 载入并执行程序。 // 默认在桌面开可见窗口执行,逐步打印中文说明,方便人工观察。 // // 运行: // DISPLAY=:1 PLAYWRIGHT_MODULE=/path/to/playwright/index.mjs \ // node docs/user-manual/local-run-demo.mjs // // 环境变量:DEMO_URL(默认 http://127.0.0.1:5174/)、DEMO_PACE_MS(每步停留,默认 2500)、 // DEMO_RUN_HOLD_MS(程序运行观察时长,默认 45000)、CHROME_PATH。 import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const here = dirname(fileURLToPath(import.meta.url)); const baseUrl = process.env.DEMO_URL ?? "http://127.0.0.1:5174/"; const chromePath = process.env.CHROME_PATH ?? "/usr/bin/google-chrome"; const pace = Number(process.env.DEMO_PACE_MS ?? 2500); const runHold = Number(process.env.DEMO_RUN_HOLD_MS ?? 45000); const shotDir = resolve(here, "local-walkthrough"); const { chromium } = await import(process.env.PLAYWRIGHT_MODULE); const browser = await chromium.launch({ executablePath: chromePath, headless: false, args: ["--no-sandbox", "--start-maximized", "--disable-infobars", "--enable-unsafe-swiftshader"], }); const page = await browser.newPage({ viewport: null }); await page.bringToFront().catch(() => {}); const wait = (ms) => page.waitForTimeout(ms); const status = async () => (await page.locator(".machine-status").innerText()).replace(/\s+/g, " ").trim(); const command = async () => (await page.locator(".command-status").innerText()).trim(); const axes = async () => page.locator(".xyzac-position-grid output").allInnerTexts(); const shot = (name) => page.screenshot({ path: resolve(shotDir, name) }); const say = (step, message) => console.log(`\n[${step}] ${message}`); const detail = async () => { const text = await status(); return `${text.match(/Task state (\S+)/)?.[1]} / ${text.match(/Interpreter (\S+)/)?.[1]} / ${text.match(/Machine sequence (NOT HOMED|HOMED)/)?.[1]}`; }; const waitFor = async (check, timeout, label) => { const deadline = Date.now() + timeout; let last = ""; while (Date.now() < deadline) { last = await check(); if (last) return last; await wait(400); } throw new Error(`${label} 超时(最后:${last || "无"})`); }; const waitStatus = (pattern, timeout = 60000) => waitFor(async () => { const text = await status(); return pattern.test(text) ? text : null; }, timeout, `等待 ${pattern}`); const waitCase = () => waitFor(async () => ((await page.locator(".case-status").innerText()).trim().startsWith("Program:") ? "ok" : null), 120000, "程序装载"); say(1, `打开网站 ${baseUrl}`); await page.goto(baseUrl, { waitUntil: "load", timeout: 60000 }); await page.waitForSelector('[data-axis-control="workspace-case-select"]', { timeout: 60000 }); await waitCase(); await waitStatus(/STATE_/, 60000); await wait(2000); console.log(` 连接:${(await page.locator(".connection-state").innerText()).trim()}|当前程序:${(await page.locator(".case-status").innerText()).trim()}|状态:${await detail()}`); await shot("demo-1-opened.png"); say(2, "机床尚未上电,确认运动操作被门控挡住"); const jogButton = page.locator('[data-axis-control="manual-jog-positive"]'); console.log(` Jog 按钮:${(await jogButton.isDisabled()) ? `禁用(${await jogButton.getAttribute("title")})` : "可用"}|任务模式:${(await status()).match(/Task mode (\S+)/)?.[1]}`); await wait(pace); say(3, "急停复位,然后上电"); const estop = page.locator('[data-axis-control="toolbar-estop"]'); if ((await estop.getAttribute("aria-label"))?.includes("Cancel")) { await estop.click(); await wait(1500); } await page.locator('[data-axis-control="toolbar-power"]').click(); const powered = await waitStatus(/STATE_ON|STATE_IDLE/, 40000); console.log(` 上电完成:${powered.match(/Task state (\S+)/)?.[1]}|反馈:${await command()}`); await shot("demo-2-powered.png"); await wait(pace); say(4, "HOME ALL 全部关节回零"); await page.locator('[data-axis-control="manual-home-all"]').click(); await waitStatus(/Machine sequence HOMED/, 60000); await wait(1500); console.log(` 回零完成:${await detail()}|轴位:${(await axes()).join(" / ")}`); await shot("demo-3-homed.png"); await wait(pace); say(5, "载入 XYZAC 船体程序"); await page.locator('[data-axis-control="workspace-load-boat"]').click(); await waitCase(); await wait(3000); console.log(` 程序:${(await page.locator(".case-status").innerText()).trim()}|反馈:${await command()}`); await shot("demo-4-program-loaded.png"); await wait(pace); say(6, "执行程序,观察解释器与轴运动"); await page.locator('[data-axis-control="toolbar-run"]').click(); await waitStatus(/STATE_RUNNING|INTERP_READING|INTERP_WAITING/, 60000); console.log(` 运行中:${await detail()}`); const samples = []; const holdUntil = Date.now() + runHold; let index = 0; while (Date.now() < holdUntil) { await wait(3000); const current = await axes(); samples.push(current.slice(0, 5).join(" / ")); index += 1; console.log(` +${String(index * 3).padStart(2)}s ${current.slice(0, 5).join(" ")}`); if (index % 3 === 0) await shot(`demo-5-running-${index / 3}.png`); } console.log(` 轴运动采样(X / Y / Z / A / C):\n ${samples.join("\n ")}`); await shot("demo-6-running-final.png"); say(7, "停止程序"); if (await page.locator('[data-axis-control="toolbar-stop"]').isEnabled()) await page.locator('[data-axis-control="toolbar-stop"]').click(); await wait(3000); console.log(` 已停止:${await detail()}|反馈:${await command()}`); await shot("demo-7-stopped.png"); console.log("\n演示结束,窗口保持 20 秒后关闭。"); await wait(20000); await browser.close();