接入 LinuxCNC TP 运行反馈
This commit is contained in:
@@ -6,12 +6,34 @@
|
||||
</head>
|
||||
<body>
|
||||
<pre id="result">gmoccapy_shell_smoke=pending</pre>
|
||||
<iframe id="app-frame" src="../../app/index.html" title="gmoccapy shell"></iframe>
|
||||
<iframe id="app-frame" title="gmoccapy shell"></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";
|
||||
frame.src = appSrc;
|
||||
|
||||
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
async function waitForInterpreterExecution(win) {
|
||||
for (let attempt = 0; attempt < 40; attempt += 1) {
|
||||
const state = win.webRtcp5AxisSimulation.getState();
|
||||
if (!state.interpreterExecutionPending && state.programExecutionSourceMode === "linuxcnc-interpreter-wasm") {
|
||||
return state;
|
||||
}
|
||||
await wait(25);
|
||||
}
|
||||
return win.webRtcp5AxisSimulation.getState();
|
||||
}
|
||||
async function waitForMachineFileExecution(win) {
|
||||
for (let attempt = 0; attempt < 60; attempt += 1) {
|
||||
const state = win.webRtcp5AxisSimulation.getState();
|
||||
if (!state.interpreterExecutionPending && state.machineFileExecution) {
|
||||
return state;
|
||||
}
|
||||
await wait(25);
|
||||
}
|
||||
return win.webRtcp5AxisSimulation.getState();
|
||||
}
|
||||
|
||||
async function runSmoke() {
|
||||
await new Promise((resolve, reject) => {
|
||||
@@ -89,25 +111,84 @@
|
||||
throw new Error(`forbidden frontend framework dependency detected: ${forbidden.join(", ")}`);
|
||||
}
|
||||
|
||||
const runtimeReadiness = await win.webRtcp5AxisSimulation.kinematicsRuntimeReady;
|
||||
if (runtimeReadiness.loaded !== true || runtimeReadiness.moduleId !== "xyzac-trt") {
|
||||
throw new Error(`LinuxCNC kinematics runtime did not load in browser: ${JSON.stringify(runtimeReadiness)}`);
|
||||
}
|
||||
if (runtimeReadiness.executionContext !== "worker") {
|
||||
throw new Error(`LinuxCNC kinematics runtime should run in worker: ${JSON.stringify(runtimeReadiness)}`);
|
||||
}
|
||||
const interpreterReadiness = await win.webRtcp5AxisSimulation.interpreterRuntimeReady;
|
||||
if (interpreterReadiness.loaded !== true || interpreterReadiness.runProgramReady !== true) {
|
||||
throw new Error(`LinuxCNC interpreter runtime did not load in browser: ${JSON.stringify(interpreterReadiness)}`);
|
||||
}
|
||||
if (interpreterReadiness.executionContext !== "worker") {
|
||||
throw new Error(`LinuxCNC interpreter runtime should run in worker: ${JSON.stringify(interpreterReadiness)}`);
|
||||
}
|
||||
if (
|
||||
interpreterReadiness.plannerRuntimeReady !== true ||
|
||||
interpreterReadiness.plannerSemanticBoundary !== "linuxcnc_tp_queue_runtime_timing_from_canonical_motion"
|
||||
) {
|
||||
throw new Error(`LinuxCNC TP planner runtime did not load in browser worker: ${JSON.stringify(interpreterReadiness)}`);
|
||||
}
|
||||
const state = win.webRtcp5AxisSimulation.getState();
|
||||
if (state.sourceMode !== "fixture-ui-only") {
|
||||
if (state.sourceMode !== "source-derived-kinematics-wasm") {
|
||||
throw new Error(`unexpected source mode: ${state.sourceMode}`);
|
||||
}
|
||||
if (state.machineProfile !== "xyzac-trt") {
|
||||
throw new Error(`unexpected profile: ${state.machineProfile}`);
|
||||
if (state.kinematicsExecutionContext !== "worker") {
|
||||
throw new Error(`unexpected kinematics execution context: ${state.kinematicsExecutionContext}`);
|
||||
}
|
||||
const profileSelector = doc.querySelector('[data-action="select-profile"]');
|
||||
if (!profileSelector || profileSelector.options.length < 2) {
|
||||
throw new Error("missing five-axis profile selector");
|
||||
}
|
||||
profileSelector.value = "xyzbc-trt";
|
||||
profileSelector.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
await wait(500);
|
||||
await win.webRtcp5AxisSimulation.refreshKinematicsFrame({ operatorMessage: "browser smoke refreshed xyzbc frame" });
|
||||
const xyzbcState = win.webRtcp5AxisSimulation.getState();
|
||||
if (xyzbcState.machineProfile !== "xyzbc-trt" || xyzbcState.profile.traj.coordinates !== "XYZBC") {
|
||||
throw new Error("profile selector did not switch to XYZBC");
|
||||
}
|
||||
if (xyzbcState.kinematicsRuntimeReadiness?.moduleId !== "xyzbc-trt") {
|
||||
throw new Error(`XYZBC kinematics runtime did not load: ${JSON.stringify(xyzbcState.kinematicsRuntimeReadiness)}`);
|
||||
}
|
||||
const restoredProfileSelector = doc.querySelector('[data-action="select-profile"]');
|
||||
restoredProfileSelector.value = "xyzac-trt";
|
||||
restoredProfileSelector.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
await wait(500);
|
||||
await win.webRtcp5AxisSimulation.refreshKinematicsFrame({ operatorMessage: "browser smoke restored xyzac frame" });
|
||||
const restoredProfileState = win.webRtcp5AxisSimulation.getState();
|
||||
if (restoredProfileState.machineProfile !== "xyzac-trt") {
|
||||
throw new Error(`unexpected profile: ${restoredProfileState.machineProfile}`);
|
||||
}
|
||||
if (state.rtcpFrame?.apiName !== "web-rtcp-5axis-motion-frame") {
|
||||
throw new Error("missing RTCP frame state");
|
||||
}
|
||||
if (state.rtcpFrame?.readiness?.linuxCncKinematicsReady !== false) {
|
||||
throw new Error("fixture RTCP frame must not claim LinuxCNC kinematics readiness");
|
||||
if (state.rtcpFrame?.readiness?.linuxCncKinematicsReady !== true) {
|
||||
throw new Error("browser RTCP frame must use LinuxCNC kinematics readiness");
|
||||
}
|
||||
if (!doc.querySelector('[data-rtcp-value="tcp"]')?.textContent.includes("TCP")) {
|
||||
throw new Error("missing TCP DRO strip");
|
||||
}
|
||||
if (!doc.querySelector('[data-rtcp-diagnostic="boundary"]')?.textContent.includes("fixture_frame_ui_plumbing")) {
|
||||
if (!doc.querySelector('[data-rtcp-diagnostic="boundary"]')?.textContent.includes("linuxcnc_kinematics_wasm_c_abi")) {
|
||||
throw new Error("missing RTCP boundary diagnostic");
|
||||
}
|
||||
if (!doc.querySelector('[data-linuxcnc-ini="status"]')?.textContent.includes("xyzac-trt.ini")) {
|
||||
throw new Error("missing LinuxCNC INI status diagnostic");
|
||||
}
|
||||
if (!doc.querySelector('[data-linuxcnc-ini="kins"]')?.textContent.includes("xyzac-trt-kins")) {
|
||||
throw new Error("missing LinuxCNC INI kinematics diagnostic");
|
||||
}
|
||||
if (!doc.querySelector('[data-linuxcnc-ini="limits"]')?.textContent.includes("X[-200,200]")) {
|
||||
throw new Error("missing LinuxCNC INI axis limits diagnostic");
|
||||
}
|
||||
if (!doc.querySelector('[data-linuxcnc-task-policy="boundary"]')?.textContent.includes("linuxcnc_task_state_mode_command_gate")) {
|
||||
throw new Error("missing LinuxCNC task policy boundary diagnostic");
|
||||
}
|
||||
if (!doc.querySelector('[data-linuxcnc-task-policy="source"]')?.textContent.includes("linuxcnc/src/emc/task/emctaskmain.cc")) {
|
||||
throw new Error("missing LinuxCNC emctaskmain source diagnostic");
|
||||
}
|
||||
if (!doc.querySelector('[data-tool-preview="summary"]')?.textContent.includes("T1")) {
|
||||
throw new Error("missing tool preview summary");
|
||||
}
|
||||
@@ -131,11 +212,19 @@
|
||||
if (!doc.querySelector('[data-machine-state="summary"]')?.textContent.includes("power on")) {
|
||||
throw new Error("machine state did not render power on");
|
||||
}
|
||||
doc.querySelector('[data-action="HOME"]').click();
|
||||
await wait(50);
|
||||
doc.querySelector('[data-action="mode-auto"]').click();
|
||||
await wait(50);
|
||||
if (!doc.querySelector('[data-linuxcnc-task-policy="gates"]')?.textContent.includes("auto")) {
|
||||
throw new Error("LinuxCNC task policy did not expose auto run gate");
|
||||
}
|
||||
|
||||
win.webRtcp5AxisSimulation.dispatch({
|
||||
type: "LOAD_PROGRAM",
|
||||
filename: "operator-demo.ngc",
|
||||
content: [
|
||||
"G90 G17",
|
||||
"G0 X0 Y0 Z0",
|
||||
"G1 X10 F100",
|
||||
"G1 Y10",
|
||||
@@ -143,18 +232,154 @@
|
||||
"G1 Y0",
|
||||
"G0 Z5",
|
||||
"M5",
|
||||
"M30",
|
||||
"M2",
|
||||
].join("\n"),
|
||||
});
|
||||
await wait(50);
|
||||
await waitForInterpreterExecution(win);
|
||||
if (win.webRtcp5AxisSimulation.getState().activeProgram !== "operator-demo.ngc") {
|
||||
throw new Error("LOAD_PROGRAM did not update active program");
|
||||
}
|
||||
if (doc.querySelector('[data-active-program-line]')?.textContent !== "Current line 1") {
|
||||
throw new Error("loaded program did not render current line 1");
|
||||
if (win.webRtcp5AxisSimulation.getState().programExecutionSourceMode !== "linuxcnc-interpreter-wasm") {
|
||||
throw new Error("LOAD_PROGRAM did not execute through LinuxCNC interpreter WASM");
|
||||
}
|
||||
if (doc.querySelector(".gcode-row.active")?.dataset.programLine !== "1") {
|
||||
throw new Error("loaded program active row should be line 1");
|
||||
if (win.webRtcp5AxisSimulation.getState().programExecution?.summary?.motionEventCount < 5) {
|
||||
throw new Error("LinuxCNC interpreter execution did not produce motion events");
|
||||
}
|
||||
if (win.webRtcp5AxisSimulation.getState().programExecution?.summary?.plannerRuntimeReady !== true) {
|
||||
throw new Error("LinuxCNC TP planner timing was not ready for operator program");
|
||||
}
|
||||
if (win.webRtcp5AxisSimulation.getState().programExecutionTiming?.semanticBoundary !== "linuxcnc_tp_queue_runtime_timing_from_canonical_motion") {
|
||||
throw new Error(`program timing did not use LinuxCNC TP queue runtime: ${JSON.stringify(win.webRtcp5AxisSimulation.getState().programExecutionTiming)}`);
|
||||
}
|
||||
if (win.webRtcp5AxisSimulation.getState().programExecutionTiming?.sampleCount < 2) {
|
||||
throw new Error("LinuxCNC TP runtime feedback samples were not exposed");
|
||||
}
|
||||
if (win.webRtcp5AxisSimulation.getState().programRuntimeFeedback?.semanticBoundary !== "linuxcnc_tp_run_cycle_feedback_without_hardware") {
|
||||
throw new Error(`initial runtime feedback did not use LinuxCNC TP sample: ${JSON.stringify(win.webRtcp5AxisSimulation.getState().programRuntimeFeedback)}`);
|
||||
}
|
||||
if (!doc.querySelector('[data-program-execution-source]')?.textContent.includes("linuxcnc-interpreter-wasm")) {
|
||||
throw new Error("program execution source DOM did not render interpreter source");
|
||||
}
|
||||
if (!doc.querySelector('[data-program-execution-summary]')?.textContent.includes("motion")) {
|
||||
throw new Error("program execution summary DOM did not render motion count");
|
||||
}
|
||||
if (!doc.querySelector('[data-program-timing="summary"]')?.textContent.match(/\\d+:\\d/)) {
|
||||
throw new Error("program timing summary DOM did not render estimated execution time");
|
||||
}
|
||||
if (!doc.querySelector('[data-program-timing="segment"]')?.textContent.includes("mm/min")) {
|
||||
throw new Error("program timing segment DOM did not render segment velocity");
|
||||
}
|
||||
if (!doc.querySelector('[data-program-runtime-feedback="source"]')?.textContent.includes("linuxcnc-tp-runtime-sample")) {
|
||||
throw new Error("program runtime feedback DOM did not render LinuxCNC TP sample source");
|
||||
}
|
||||
if (!doc.querySelector('[data-program-runtime-feedback="dtg"]')?.textContent.includes("DTG")) {
|
||||
throw new Error("program runtime feedback DOM did not render DTG");
|
||||
}
|
||||
if (!doc.querySelector('[data-program-switchkins-summary]')?.textContent.includes("0 events")) {
|
||||
throw new Error("program switchkins summary DOM did not render zero-event state");
|
||||
}
|
||||
win.webRtcp5AxisSimulation.dispatch({
|
||||
type: "LOAD_PROGRAM",
|
||||
filename: "operator-arc-demo.ngc",
|
||||
content: [
|
||||
"G90 G17",
|
||||
"G0 X1 Y0 Z0",
|
||||
"G2 X0 Y1 I-1 J0 F60",
|
||||
"M2",
|
||||
].join("\n"),
|
||||
});
|
||||
await waitForInterpreterExecution(win);
|
||||
const arcState = win.webRtcp5AxisSimulation.getState();
|
||||
if (!arcState.programExecution?.summary?.motionTypes?.includes("ARC_FEED")) {
|
||||
throw new Error("LinuxCNC interpreter did not produce ARC_FEED for browser arc program");
|
||||
}
|
||||
if (arcState.programExecution?.summary?.plannerRuntimeReady !== true) {
|
||||
throw new Error("LinuxCNC TP planner timing was not ready for browser arc program");
|
||||
}
|
||||
const arcSegment = arcState.programExecutionTiming?.segments?.find((segment) => segment.type === "ARC_FEED");
|
||||
if (!arcSegment || arcSegment.durationSeconds <= 1.4) {
|
||||
throw new Error(`LinuxCNC TP arc timing segment missing or too short: ${JSON.stringify(arcState.programExecutionTiming)}`);
|
||||
}
|
||||
const stagedMachineFiles = await win.webRtcp5AxisSimulation.stageMachineFiles();
|
||||
await wait(50);
|
||||
if (stagedMachineFiles.save.status !== "saved" || stagedMachineFiles.save.fileCount < 5) {
|
||||
throw new Error(`machine file staging did not save files: ${JSON.stringify(stagedMachineFiles.save)}`);
|
||||
}
|
||||
if (!stagedMachineFiles.save.files.some((file) => file.sourceRel.endsWith("remap_subs/428remap.ngc"))) {
|
||||
throw new Error("machine file staging did not include M428 remap");
|
||||
}
|
||||
if (!doc.querySelector('[data-machine-file-staging="status"]')?.textContent.includes("staged")) {
|
||||
throw new Error("machine file staging status did not render");
|
||||
}
|
||||
const sourceSelect = doc.querySelector('[data-action="select-linuxcnc-gcode-source"]');
|
||||
if (!sourceSelect || sourceSelect.options.length < 4) {
|
||||
throw new Error("LinuxCNC 5-axis G-code source selector did not render staged demos");
|
||||
}
|
||||
const impellerSource = "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/impeller-7bl-xyzac.ngc";
|
||||
sourceSelect.value = impellerSource;
|
||||
sourceSelect.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
await waitForInterpreterExecution(win);
|
||||
const linuxCncSourceState = win.webRtcp5AxisSimulation.getState();
|
||||
if (linuxCncSourceState.programSource !== "linuxcnc-vendored-5axis-gcode") {
|
||||
throw new Error(`LinuxCNC source program was not loaded: ${linuxCncSourceState.programSource}`);
|
||||
}
|
||||
if (!linuxCncSourceState.activeProgram.endsWith("impeller-7bl-xyzac.ngc")) {
|
||||
throw new Error(`unexpected LinuxCNC source program: ${linuxCncSourceState.activeProgram}`);
|
||||
}
|
||||
if (!doc.querySelector('[data-linuxcnc-gcode-source="selected"]')?.textContent.includes("impeller-7bl-xyzac.ngc")) {
|
||||
throw new Error("selected LinuxCNC G-code source did not render");
|
||||
}
|
||||
if (!doc.querySelector('[data-program-source]')?.textContent.includes("linuxcnc-vendored-5axis-gcode")) {
|
||||
throw new Error("program source DOM did not show LinuxCNC vendored source");
|
||||
}
|
||||
win.webRtcp5AxisSimulation.dispatch({ type: "RUN_MACHINE_FILE_PROGRAM" });
|
||||
const machineFileRunState = await waitForMachineFileExecution(win);
|
||||
if (machineFileRunState.machineFileExecution?.summary?.machineFileExecutionReady !== true) {
|
||||
throw new Error(`machine-file backed remap run did not complete: ${JSON.stringify(machineFileRunState.machineFileExecution?.summary)}`);
|
||||
}
|
||||
if (!machineFileRunState.machineFileExecution.resultText.includes("fiveaxis_file_reached_exit=1")) {
|
||||
throw new Error("machine-file backed remap run did not reach exit");
|
||||
}
|
||||
if (!machineFileRunState.machineFileExecution.machineFilePlan?.selectedProgramFilename?.includes("impeller-7bl-xyzac.ngc")) {
|
||||
throw new Error("machine-file backed remap run did not use selected LinuxCNC G-code source");
|
||||
}
|
||||
if (!doc.querySelector('[data-machine-file-execution="status"]')?.textContent.includes("ready")) {
|
||||
throw new Error("machine-file execution status did not render");
|
||||
}
|
||||
if (machineFileRunState.fullExecutionBoundary?.machineFileBackedRemapReady !== true) {
|
||||
throw new Error(`full execution boundary did not record remap readiness: ${JSON.stringify(machineFileRunState.fullExecutionBoundary)}`);
|
||||
}
|
||||
if (machineFileRunState.fullExecutionBoundary?.fullLinuxCncProgramExecutionReady !== false) {
|
||||
throw new Error("full LinuxCNC program execution must remain blocked without native task/planner/HAL");
|
||||
}
|
||||
if (!machineFileRunState.fullExecutionBoundary.blockers.some((blocker) => blocker.includes("trajectory planner"))) {
|
||||
throw new Error("full execution boundary did not expose planner blocker");
|
||||
}
|
||||
if (!doc.querySelector('[data-full-execution-boundary="status"]')?.textContent.includes("remap ready")) {
|
||||
throw new Error("full execution boundary status did not render remap readiness");
|
||||
}
|
||||
if (!doc.querySelector('[data-full-execution-boundary="status"]')?.textContent.includes("full blocked")) {
|
||||
throw new Error("full execution boundary status did not render full blocked state");
|
||||
}
|
||||
if (!doc.querySelector('[data-full-execution-boundary="blockers"]')?.textContent.includes("native LinuxCNC task")) {
|
||||
throw new Error("full execution blocker diagnostic did not render native task blocker");
|
||||
}
|
||||
if (!doc.querySelector('[data-full-execution-boundary="evidence"]')?.textContent.includes("linuxcnc_machine_file_remap_ready_planner_task_hal_blocked")) {
|
||||
throw new Error("full execution evidence diagnostic did not render boundary semantic");
|
||||
}
|
||||
const savedSession = await win.webRtcp5AxisSimulation.saveSession();
|
||||
await wait(50);
|
||||
if (savedSession.snapshot.format !== "web-rtcp-5axis-session-snapshot") {
|
||||
throw new Error("saveSession did not write a five-axis session snapshot");
|
||||
}
|
||||
if (!doc.querySelector('[data-session-persistence="status"]')?.textContent.includes("saved")) {
|
||||
throw new Error("session save status did not render");
|
||||
}
|
||||
if (doc.querySelector('[data-active-program-line]')?.textContent !== "Current line 2") {
|
||||
throw new Error("loaded program did not render first LinuxCNC motion line");
|
||||
}
|
||||
if (doc.querySelector(".gcode-row.active")?.dataset.programLine !== "2") {
|
||||
throw new Error("loaded program active row should be first LinuxCNC motion line");
|
||||
}
|
||||
|
||||
win.webRtcp5AxisSimulation.dispatch({ type: "RUN" });
|
||||
@@ -162,8 +387,29 @@
|
||||
if (win.webRtcp5AxisSimulation.getState().runState !== "running") {
|
||||
throw new Error("RUN action did not update state after power on");
|
||||
}
|
||||
if (doc.querySelector(".gcode-row.active")?.dataset.programLine !== "6") {
|
||||
throw new Error("RUN did not highlight the executing current line");
|
||||
if (win.webRtcp5AxisSimulation.getState().programRuntimeFeedback?.sourceMode !== "linuxcnc-tp-runtime-sample") {
|
||||
throw new Error("RUN did not advance with LinuxCNC TP runtime feedback");
|
||||
}
|
||||
if (win.webRtcp5AxisSimulation.getState().feed.currentVelocity <= 0) {
|
||||
throw new Error("RUN did not expose LinuxCNC TP current velocity");
|
||||
}
|
||||
if (Number(doc.querySelector(".gcode-row.active")?.dataset.programLine || 0) < 2) {
|
||||
throw new Error("RUN did not highlight a LinuxCNC canonical motion line");
|
||||
}
|
||||
doc.querySelector('[data-action="PAUSE"]').click();
|
||||
await wait(50);
|
||||
if (win.webRtcp5AxisSimulation.getState().runState !== "paused") {
|
||||
throw new Error("PAUSE action did not update state");
|
||||
}
|
||||
doc.querySelector('[data-action="RUN"]').click();
|
||||
await wait(50);
|
||||
if (!win.webRtcp5AxisSimulation.getState().operatorMessage.includes("resume paused program")) {
|
||||
throw new Error("RUN should be blocked while program is paused");
|
||||
}
|
||||
doc.querySelector('[data-action="RESUME"]').click();
|
||||
await wait(50);
|
||||
if (win.webRtcp5AxisSimulation.getState().runState !== "running") {
|
||||
throw new Error("RESUME action did not restore running state");
|
||||
}
|
||||
const tcpButton = doc.querySelector('[data-action="kins-tcp"]');
|
||||
tcpButton.click();
|
||||
@@ -181,8 +427,11 @@
|
||||
if (canvas.dataset.threeRtcpState !== "on") {
|
||||
throw new Error("Three.js preview did not consume RTCP enabled frame");
|
||||
}
|
||||
if (doc.querySelector('[data-rtcp-diagnostic="kinematics-ready"]')?.textContent !== "pending") {
|
||||
throw new Error("RTCP diagnostics must keep LinuxCNC kinematics pending for fixture mode");
|
||||
if (doc.querySelector('[data-rtcp-diagnostic="kinematics-ready"]')?.textContent !== "ready") {
|
||||
throw new Error("RTCP diagnostics must show LinuxCNC kinematics ready");
|
||||
}
|
||||
if (doc.querySelector('[data-rtcp-diagnostic="execution-context"]')?.textContent !== "worker") {
|
||||
throw new Error("RTCP diagnostics must show worker kinematics context");
|
||||
}
|
||||
if (!doc.querySelector('[data-linuxcnc-boundary="adapter"]')?.textContent.includes("linuxcnc-boundary-adapter")) {
|
||||
throw new Error("missing LinuxCNC boundary adapter diagnostic");
|
||||
@@ -193,12 +442,19 @@
|
||||
if (!doc.querySelector('[data-linuxcnc-boundary="profile-summary"]')?.textContent.includes("XYZAC / 5 joints / 10 tools")) {
|
||||
throw new Error("missing LinuxCNC profile summary diagnostic");
|
||||
}
|
||||
if (!doc.querySelector('[data-linuxcnc-boundary="readiness"]')?.textContent.includes("blocked")) {
|
||||
throw new Error("LinuxCNC boundary readiness should remain blocked");
|
||||
if (!doc.querySelector('[data-linuxcnc-boundary="readiness"]')?.textContent.includes("ready")) {
|
||||
throw new Error("LinuxCNC boundary readiness should show kinematics ready");
|
||||
}
|
||||
if (!doc.querySelector('[data-linuxcnc-boundary="interpreter"]')?.textContent.includes("linuxcnc_interpreter_wasm_canonical_events")) {
|
||||
throw new Error("missing LinuxCNC interpreter boundary diagnostic");
|
||||
}
|
||||
if (doc.querySelector('[data-linuxcnc-boundary="interpreter-context"]')?.textContent !== "worker") {
|
||||
throw new Error("LinuxCNC interpreter diagnostics must show worker context");
|
||||
}
|
||||
canvas = doc.querySelector("[data-five-axis-canvas]");
|
||||
const tcpPoseBeforeStep = canvas.dataset.threeTcpPose;
|
||||
win.webRtcp5AxisSimulation.dispatch({ type: "STEP" });
|
||||
await win.webRtcp5AxisSimulation.refreshKinematicsFrame({ operatorMessage: "browser smoke refreshed worker frame" });
|
||||
await wait(50);
|
||||
if (win.webRtcp5AxisSimulation.getState().activeLine <= rtcpState.activeLine) {
|
||||
throw new Error("STEP did not advance RTCP frame line");
|
||||
@@ -208,6 +464,13 @@
|
||||
throw new Error("Three.js preview did not update TCP pose after STEP");
|
||||
}
|
||||
assertCanvasNonblank(canvas, "updated Three.js preview");
|
||||
doc.querySelector('[data-action="STOP"]').click();
|
||||
await wait(50);
|
||||
if (win.webRtcp5AxisSimulation.getState().machine.interpState !== "idle") {
|
||||
throw new Error("STOP did not clear interpreter state");
|
||||
}
|
||||
doc.querySelector('[data-action="mode-manual"]').click();
|
||||
await wait(50);
|
||||
doc.querySelector('[data-action="mode-jog"]').click();
|
||||
await wait(50);
|
||||
const xBeforeJog = win.webRtcp5AxisSimulation.getState().axisPose.x;
|
||||
@@ -216,11 +479,47 @@
|
||||
if (win.webRtcp5AxisSimulation.getState().axisPose.x <= xBeforeJog) {
|
||||
throw new Error("JOG X+ did not move the axis");
|
||||
}
|
||||
await win.webRtcp5AxisSimulation.restoreSession();
|
||||
await wait(50);
|
||||
const restoredState = win.webRtcp5AxisSimulation.getState();
|
||||
if (restoredState.axisPose.x !== xBeforeJog) {
|
||||
throw new Error("restoreSession did not restore saved axis pose");
|
||||
}
|
||||
if (restoredState.programExecutionSourceMode !== "linuxcnc-interpreter-wasm") {
|
||||
throw new Error("restoreSession did not restore LinuxCNC interpreter execution source");
|
||||
}
|
||||
if (!doc.querySelector('[data-session-persistence="status"]')?.textContent.includes("restored")) {
|
||||
throw new Error("session restore status did not render");
|
||||
}
|
||||
if (win.webRtcp5AxisSimulation.getState().machine.allHomed !== true) {
|
||||
doc.querySelector('[data-action="mode-manual"]').click();
|
||||
await wait(50);
|
||||
doc.querySelector('[data-action="HOME"]').click();
|
||||
await wait(50);
|
||||
}
|
||||
doc.querySelector('[data-action="mode-mdi"]').click();
|
||||
const mdiInput = doc.querySelector('[data-action="mdi-command"]');
|
||||
if (!mdiInput) {
|
||||
throw new Error("missing MDI command input");
|
||||
}
|
||||
mdiInput.value = "G90 X12.5 Y-4 Z1.25 F900";
|
||||
doc.querySelector('[data-action="mdi-submit"]').click();
|
||||
await wait(50);
|
||||
let mdiState = win.webRtcp5AxisSimulation.getState();
|
||||
if (mdiState.runState !== "mdi") {
|
||||
throw new Error("MDI action did not update run state");
|
||||
}
|
||||
if (mdiState.axisPose.x !== 12.5 || mdiState.axisPose.y !== -4 || mdiState.axisPose.z !== 1.25) {
|
||||
throw new Error(`MDI input did not move axes: ${JSON.stringify(mdiState.axisPose)}`);
|
||||
}
|
||||
if (!doc.querySelector(".mdi-history")?.textContent.includes("G90 X12.5")) {
|
||||
throw new Error("MDI history did not render executed command");
|
||||
}
|
||||
doc.querySelector('[data-action="MDI_RUN"]').click();
|
||||
await wait(50);
|
||||
if (win.webRtcp5AxisSimulation.getState().runState !== "mdi") {
|
||||
throw new Error("MDI action did not update run state");
|
||||
mdiState = win.webRtcp5AxisSimulation.getState();
|
||||
if (mdiState.runState !== "mdi" || mdiState.machine.mdiCommand !== "G90 X12.5 Y-4 Z1.25 F900") {
|
||||
throw new Error("bottom MDI button did not execute staged command");
|
||||
}
|
||||
doc.querySelector('[data-action="reset"]').click();
|
||||
await wait(50);
|
||||
@@ -272,6 +571,8 @@
|
||||
if (win.webRtcp5AxisSimulation.getState().preview.fullscreen !== true) {
|
||||
throw new Error("fullscreen button did not update state");
|
||||
}
|
||||
doc.querySelector('[data-action="mode-manual"]').click();
|
||||
await wait(50);
|
||||
doc.querySelector('[data-action="HOME"]').click();
|
||||
await wait(50);
|
||||
const homedState = win.webRtcp5AxisSimulation.getState();
|
||||
|
||||
77
web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh
Executable file
77
web-rtcp-5axis-sim-plan/tests/browser/verify_gmoccapy_dist_browser.sh
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
|
||||
CHROMIUM="${CHROMIUM:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true)}"
|
||||
|
||||
if [[ -z "$CHROMIUM" ]]; then
|
||||
echo "missing Chromium-compatible browser; set CHROMIUM=/path/to/browser" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
npm --prefix "$ROOT_DIR/web-rtcp-5axis-sim-plan/app" run build >/dev/null
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
PORT_FILE="$TMP_DIR/port"
|
||||
SERVER_LOG="$TMP_DIR/server.log"
|
||||
CHROME_PROFILE="$TMP_DIR/chrome-profile"
|
||||
mkdir -p "$CHROME_PROFILE"
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${SERVER_PID:-}" ]]; then
|
||||
kill "$SERVER_PID" 2>/dev/null || true
|
||||
wait "$SERVER_PID" 2>/dev/null || true
|
||||
fi
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
python3 - <<'PY' "$ROOT_DIR" "$PORT_FILE" >"$SERVER_LOG" 2>&1 &
|
||||
import functools
|
||||
import http.server
|
||||
import pathlib
|
||||
import socketserver
|
||||
import sys
|
||||
|
||||
root = pathlib.Path(sys.argv[1])
|
||||
port_file = pathlib.Path(sys.argv[2])
|
||||
|
||||
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(root))
|
||||
with socketserver.TCPServer(("127.0.0.1", 0), handler) as httpd:
|
||||
port_file.write_text(str(httpd.server_address[1]), encoding="ascii")
|
||||
httpd.serve_forever()
|
||||
PY
|
||||
SERVER_PID=$!
|
||||
|
||||
for _ in $(seq 1 100); do
|
||||
[[ -s "$PORT_FILE" ]] && break
|
||||
sleep 0.05
|
||||
done
|
||||
|
||||
if [[ ! -s "$PORT_FILE" ]]; then
|
||||
echo "gmoccapy dist browser smoke HTTP server did not start" >&2
|
||||
cat "$SERVER_LOG" >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PORT="$(cat "$PORT_FILE")"
|
||||
APP_PATH="../../app/dist/index.html"
|
||||
URL="http://127.0.0.1:$PORT/web-rtcp-5axis-sim-plan/tests/browser/gmoccapy_shell_smoke.html?app=$APP_PATH"
|
||||
OUT="$TMP_DIR/chromium-gmoccapy-dist.out"
|
||||
|
||||
"$CHROMIUM" \
|
||||
--headless=new \
|
||||
--disable-gpu \
|
||||
--no-sandbox \
|
||||
--user-data-dir="$CHROME_PROFILE" \
|
||||
--virtual-time-budget=10000 \
|
||||
--dump-dom \
|
||||
"$URL" >"$OUT" 2>&1
|
||||
|
||||
if ! grep -Fq "gmoccapy_shell_smoke=ok" "$OUT"; then
|
||||
echo "gmoccapy dist browser smoke failed" >&2
|
||||
sed -n '1,260p' "$OUT" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "gmoccapy_dist_smoke=ok"
|
||||
@@ -0,0 +1,73 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { createFiveAxisSessionPayload, createMemorySessionStorage } from "../../app/src/runtime/five-axis-session.js";
|
||||
import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc-interpreter-runtime.js";
|
||||
import { createSimulationStore } from "../../app/src/state/store.js";
|
||||
|
||||
async function waitForInterpreterExecution(store) {
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
const state = store.getState();
|
||||
if (!state.interpreterExecutionPending && state.programExecutionSourceMode === "linuxcnc-interpreter-wasm") {
|
||||
return state;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
return store.getState();
|
||||
}
|
||||
|
||||
const store = createSimulationStore();
|
||||
const interpreterRuntime = await createLinuxCncInterpreterRuntime();
|
||||
|
||||
store.dispatch({ type: "ATTACH_INTERPRETER_RUNTIME", runtime: interpreterRuntime });
|
||||
store.dispatch({ type: "TOGGLE_POWER" });
|
||||
store.dispatch({ type: "HOME" });
|
||||
store.dispatch({ type: "SET_MODE", mode: "auto" });
|
||||
store.dispatch({ type: "SET_RTCP", enabled: true });
|
||||
store.dispatch({
|
||||
type: "LOAD_PROGRAM",
|
||||
filename: "session-demo.ngc",
|
||||
content: [
|
||||
"G90 G17",
|
||||
"G0 X0 Y0 Z0",
|
||||
"G1 X4 Y5 A6 C7 F100",
|
||||
"M2",
|
||||
].join("\n"),
|
||||
});
|
||||
await waitForInterpreterExecution(store);
|
||||
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.programExecution.sourceMode, "linuxcnc-interpreter-wasm");
|
||||
assert.equal(payload.rtcpState, "on");
|
||||
assert.equal(payload.programRuntimeFeedback.sourceMode, "linuxcnc-tp-runtime-sample");
|
||||
assert.equal(payload.programRuntimeFeedback.semanticBoundary, "linuxcnc_tp_run_cycle_feedback_without_hardware");
|
||||
|
||||
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");
|
||||
|
||||
store.dispatch({ type: "SET_RTCP", enabled: false });
|
||||
store.dispatch({ type: "STOP" });
|
||||
store.dispatch({ type: "SET_MODE", mode: "manual" });
|
||||
store.dispatch({ type: "JOG", axis: "x", direction: 1, increment: 25 });
|
||||
assert.notEqual(store.getState().rtcpState, beforeSave.rtcpState);
|
||||
assert.notEqual(store.getState().axisPose.x, beforeSave.axisPose.x);
|
||||
|
||||
const restored = await store.restoreSession({ storage });
|
||||
const afterRestore = store.getState();
|
||||
assert.equal(restored.path, saved.path);
|
||||
assert.equal(afterRestore.sessionPersistence.status, "restored");
|
||||
assert.equal(afterRestore.rtcpState, beforeSave.rtcpState);
|
||||
assert.equal(afterRestore.kinsType, beforeSave.kinsType);
|
||||
assert.equal(afterRestore.axisPose.x, beforeSave.axisPose.x);
|
||||
assert.equal(afterRestore.activeProgram, "session-demo.ngc");
|
||||
assert.equal(afterRestore.programExecution.sourceMode, "linuxcnc-interpreter-wasm");
|
||||
assert.equal(afterRestore.programExecution.summary.motionEventCount, beforeSave.programExecution.summary.motionEventCount);
|
||||
assert.equal(afterRestore.programRuntimeFeedback.semanticBoundary, beforeSave.programRuntimeFeedback.semanticBoundary);
|
||||
assert.equal(afterRestore.programRuntimeFeedback.axisPose.x, beforeSave.programRuntimeFeedback.axisPose.x);
|
||||
|
||||
console.log("five_axis_session_smoke=ok");
|
||||
@@ -0,0 +1,108 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { createFullLinuxCncExecutionBoundary } from "../../app/src/runtime/full-execution-boundary.js";
|
||||
|
||||
const blocked = createFullLinuxCncExecutionBoundary({});
|
||||
assert.equal(blocked.apiName, "web-rtcp-5axis-full-linuxcnc-execution-boundary");
|
||||
assert.equal(blocked.phase, "blocked");
|
||||
assert.equal(blocked.promotionAllowed, false);
|
||||
assert.equal(blocked.fullLinuxCncProgramExecutionReady, false);
|
||||
assert.equal(blocked.missing.includes("linuxcnc kinematics WASM frame"), true);
|
||||
assert.equal(blocked.blockers.some((blocker) => blocker.includes("native LinuxCNC task")), true);
|
||||
|
||||
const canonicalState = {
|
||||
machineProfile: "xyzac-trt",
|
||||
linuxCncBoundaryAdapter: {
|
||||
linuxCncKinematicsReady: true,
|
||||
linuxCncInterpreterReady: true,
|
||||
},
|
||||
rtcpFrame: {
|
||||
semanticBoundary: "linuxcnc_kinematics_wasm_c_abi",
|
||||
readiness: { linuxCncKinematicsReady: true },
|
||||
},
|
||||
interpreterRuntimeReadiness: {
|
||||
loaded: true,
|
||||
semanticBoundary: "linuxcnc_interpreter_wasm_canonical_events",
|
||||
},
|
||||
programExecutionSourceMode: "linuxcnc-interpreter-wasm",
|
||||
programExecution: {
|
||||
sourceMode: "linuxcnc-interpreter-wasm",
|
||||
summary: {
|
||||
motionEventCount: 3,
|
||||
canonicalEventCount: 8,
|
||||
},
|
||||
},
|
||||
};
|
||||
const canonical = createFullLinuxCncExecutionBoundary(canonicalState);
|
||||
|
||||
assert.equal(canonical.readyForUiSimulation, true);
|
||||
assert.equal(canonical.machineFileBackedRemapReady, false);
|
||||
assert.equal(canonical.semanticBoundary, "linuxcnc_interpreter_canonical_ready_planner_task_hal_blocked");
|
||||
assert.equal(canonical.satisfied.includes("canonical-motion-events"), true);
|
||||
assert.equal(canonical.plannerRuntimeReady, false);
|
||||
assert.equal(canonical.missing.includes("machine-file backed five-axis remap run"), true);
|
||||
|
||||
const canonicalWithPlanner = createFullLinuxCncExecutionBoundary({
|
||||
...canonicalState,
|
||||
programExecution: {
|
||||
...canonicalState.programExecution,
|
||||
plannerTiming: {
|
||||
plannerRuntimeReady: true,
|
||||
semanticBoundary: "linuxcnc_tp_queue_runtime_timing_from_canonical_motion",
|
||||
},
|
||||
summary: {
|
||||
...canonicalState.programExecution.summary,
|
||||
plannerRuntimeReady: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(canonicalWithPlanner.plannerRuntimeReady, true);
|
||||
assert.equal(canonicalWithPlanner.satisfied.includes("linuxcnc-tp-queue-runtime-timing"), true);
|
||||
assert.equal(canonicalWithPlanner.nativeTaskReady, false);
|
||||
assert.equal(canonicalWithPlanner.nativeHalSyncReady, false);
|
||||
|
||||
const remap = createFullLinuxCncExecutionBoundary({
|
||||
...canonicalState,
|
||||
programExecution: {
|
||||
...canonicalState.programExecution,
|
||||
plannerTiming: {
|
||||
plannerRuntimeReady: true,
|
||||
semanticBoundary: "linuxcnc_tp_queue_runtime_timing_from_canonical_motion",
|
||||
},
|
||||
summary: {
|
||||
...canonicalState.programExecution.summary,
|
||||
plannerRuntimeReady: true,
|
||||
},
|
||||
},
|
||||
machineFileStaging: {
|
||||
status: "staged",
|
||||
fileCount: 12,
|
||||
},
|
||||
machineFileExecution: {
|
||||
sourceMode: "linuxcnc-machine-file-remap-wasm",
|
||||
summary: {
|
||||
machineFileExecutionReady: true,
|
||||
},
|
||||
resultText: [
|
||||
"fiveaxis_ini_open=1",
|
||||
"fiveaxis_remaps_ready=1",
|
||||
"fiveaxis_file_reached_exit=1",
|
||||
"fiveaxis_hal_switchkins: rc=0 found=1 value=0",
|
||||
].join("\n"),
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(remap.machineFileBackedRemapReady, true);
|
||||
assert.equal(remap.remapRuntimeReady, true);
|
||||
assert.equal(remap.halSwitchkinsEvidenceReady, true);
|
||||
assert.equal(remap.plannerRuntimeReady, true);
|
||||
assert.equal(remap.nativeTaskReady, false);
|
||||
assert.equal(remap.nativeHalSyncReady, false);
|
||||
assert.equal(remap.fullLinuxCncProgramExecutionReady, false);
|
||||
assert.equal(remap.promotionAllowed, false);
|
||||
assert.equal(remap.semanticBoundary, "linuxcnc_machine_file_remap_ready_planner_task_hal_blocked");
|
||||
assert.equal(remap.satisfied.includes("fiveaxis-remap-machine-file-run"), true);
|
||||
assert.equal(remap.evidence.machineFileFlags.length, 3);
|
||||
|
||||
console.log("full_execution_boundary_smoke=ok");
|
||||
@@ -0,0 +1,109 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import {
|
||||
createLinuxCncInterpSdk,
|
||||
createLinuxCncKinematicsSdk,
|
||||
planSimConfigStaging,
|
||||
} from "../../../wasm-port/runtime/sdk/src/index.js";
|
||||
import { parseLinuxCncIni } from "../../app/src/runtime/linuxcnc-ini-runtime.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const workspaceRoot = resolve(__dirname, "../../..");
|
||||
const wasmPortRoot = resolve(workspaceRoot, "wasm-port");
|
||||
const vendorRoot = resolve(wasmPortRoot, "vendor/linuxcnc");
|
||||
const trtConfigRel = "configs/sim/axis/vismach/5axis/table-rotary-tilting";
|
||||
|
||||
function near(actual, expected, tolerance = 1e-7) {
|
||||
return Math.abs(actual - expected) <= tolerance;
|
||||
}
|
||||
|
||||
function assertPoseComponent(pose, key, expected, label) {
|
||||
assert.equal(near(Number(pose[key] ?? 0), expected), true, `${label}.${key}: ${pose[key]} != ${expected}`);
|
||||
}
|
||||
|
||||
async function verifyTrtKinematics(moduleId, joints, expectedAxis) {
|
||||
const wasmFile = `linuxcnc_${moduleId.replaceAll("-", "_")}_kinematics.wasm`;
|
||||
const kins = await createLinuxCncKinematicsSdk({
|
||||
moduleId,
|
||||
moduleOptions: {
|
||||
wasmBinary: readFileSync(resolve(wasmPortRoot, "build/wasm/kinematics", wasmFile)),
|
||||
print() {},
|
||||
printErr() {},
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(kins.apiName, "linuxcnc-kinematics-wasm-sdk");
|
||||
assert.equal(kins.switchable(), 1);
|
||||
assert.equal(kins.switchKinematics(0), 0);
|
||||
|
||||
const forward = kins.forward(joints);
|
||||
assert.equal(forward.rc, 0, `${moduleId} forward`);
|
||||
|
||||
const inverse = kins.inverse(forward.pose, 5, { seedJoints: joints });
|
||||
assert.equal(inverse.rc, 0, `${moduleId} inverse`);
|
||||
inverse.joints.slice(0, 5).forEach((joint, index) => {
|
||||
assert.equal(near(joint, joints[index]), true, `${moduleId} inverse joint ${index}`);
|
||||
});
|
||||
|
||||
assert.equal(kins.switchKinematics(1), 0);
|
||||
const identity = kins.forward(joints);
|
||||
assert.equal(identity.rc, 0, `${moduleId} identity forward`);
|
||||
assertPoseComponent(identity.pose, "x", joints[0], `${moduleId} identity`);
|
||||
assertPoseComponent(identity.pose, "y", joints[1], `${moduleId} identity`);
|
||||
assertPoseComponent(identity.pose, "z", joints[2], `${moduleId} identity`);
|
||||
assertPoseComponent(identity.pose, expectedAxis, joints[3], `${moduleId} identity`);
|
||||
assertPoseComponent(identity.pose, "c", joints[4], `${moduleId} identity`);
|
||||
}
|
||||
|
||||
await verifyTrtKinematics("xyzac-trt", [10, 20, 30, 25, 40], "a");
|
||||
await verifyTrtKinematics("xyzbc-trt", [10, 20, 30, 35, 40], "b");
|
||||
|
||||
const manifestText = readFileSync(resolve(wasmPortRoot, "tools/source-manifest.txt"), "utf8");
|
||||
for (const iniFile of ["xyzac-trt.ini", "xyzbc-trt.ini"]) {
|
||||
const iniText = readFileSync(resolve(vendorRoot, trtConfigRel, iniFile), "utf8");
|
||||
const iniConfig = parseLinuxCncIni(iniText, {
|
||||
path: `${trtConfigRel}/${iniFile}`,
|
||||
profileId: iniFile.startsWith("xyzbc") ? "xyzbc-trt" : "xyzac-trt",
|
||||
});
|
||||
assert.equal(iniConfig.validation.ready, true, `${iniFile} INI ready`);
|
||||
assert.equal(iniConfig.kinematics.name.endsWith("-trt-kins"), true, `${iniFile} kins`);
|
||||
assert.deepEqual(iniConfig.halui.mdiCommands, ["M429", "M428", "M430"], `${iniFile} HALUI MDI`);
|
||||
|
||||
const plan = planSimConfigStaging({
|
||||
manifestText,
|
||||
machineRel: `axis/vismach/5axis/table-rotary-tilting`,
|
||||
iniFile,
|
||||
iniText,
|
||||
});
|
||||
const staged = plan.files.map((file) => file.sourceRel);
|
||||
assert.equal(staged.includes(`${trtConfigRel}/${iniFile}`), true, `${iniFile} staged`);
|
||||
assert.equal(staged.some((file) => file.endsWith("/remap_subs/428remap.ngc")), true, `${iniFile} M428 remap staged`);
|
||||
assert.equal(staged.some((file) => file.endsWith("/remap_subs/429remap.ngc")), true, `${iniFile} M429 remap staged`);
|
||||
assert.equal(staged.some((file) => file.endsWith("/remap_subs/430remap.ngc")), true, `${iniFile} M430 remap staged`);
|
||||
assert.equal(staged.some((file) => file.endsWith(".tbl")), true, `${iniFile} tool table staged`);
|
||||
}
|
||||
|
||||
const interp = await createLinuxCncInterpSdk({
|
||||
wasmBinary: readFileSync(resolve(wasmPortRoot, "build/wasm/core/linuxcnc_interp.wasm")),
|
||||
print() {},
|
||||
printErr() {},
|
||||
});
|
||||
const directFiveAxisProgram = [
|
||||
"G90 G17",
|
||||
"G0 X0 Y0 Z0 A0 C0",
|
||||
"G1 X10 Y2 Z-1 A15 C30 F120",
|
||||
"G1 X0 Y0 Z0 A0 C0 F120",
|
||||
"M2",
|
||||
"",
|
||||
].join("\n");
|
||||
const output = interp.runProgram(directFiveAxisProgram);
|
||||
assert.equal(output.includes("canon_event=STRAIGHT_TRAVERSE"), true);
|
||||
assert.equal(output.includes("canon_event=STRAIGHT_FEED"), true);
|
||||
assert.equal(output.includes("a=15"), true);
|
||||
assert.equal(output.includes("c=30"), true);
|
||||
|
||||
console.log("full_linuxcnc_5axis_source_node_smoke=ok");
|
||||
@@ -0,0 +1,57 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
||||
import {
|
||||
applyIniConfigToProfile,
|
||||
parseLinuxCncIni,
|
||||
} from "../../app/src/runtime/linuxcnc-ini-runtime.js";
|
||||
import { xyzacTrtProfile } from "../../app/src/profiles/xyzac-trt.js";
|
||||
import { xyzbcTrtProfile } from "../../app/src/profiles/xyzbc-trt.js";
|
||||
|
||||
const xyzacIniText = await readFile(
|
||||
new URL("../../../wasm-port/vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const xyzbcIniText = await readFile(
|
||||
new URL("../../../wasm-port/vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzbc-trt.ini", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const xyzacIni = parseLinuxCncIni(xyzacIniText, {
|
||||
path: xyzacTrtProfile.iniPath,
|
||||
profileId: xyzacTrtProfile.id,
|
||||
});
|
||||
assert.equal(xyzacIni.apiName, "web-rtcp-5axis-linuxcnc-ini-config");
|
||||
assert.equal(xyzacIni.validation.ready, true);
|
||||
assert.equal(xyzacIni.machineName, "sim-xyzac-trt-kins (switchkins)");
|
||||
assert.equal(xyzacIni.kinematics.name, "xyzac-trt-kins");
|
||||
assert.equal(xyzacIni.kinematicsParameters.sparm, "identityfirst");
|
||||
assert.equal(xyzacIni.kinematicsParameters.joints, 5);
|
||||
assert.equal(xyzacIni.traj.coordinates, "XYZAC");
|
||||
assert.equal(xyzacIni.axisLimits.A.max, 50);
|
||||
assert.equal(xyzacIni.jointConfig[3].axis, "A");
|
||||
assert.equal(xyzacIni.jointConfig[4].max, 36000);
|
||||
assert.deepEqual(xyzacIni.halui.mdiCommands, ["M429", "M428", "M430"]);
|
||||
assert.equal(xyzacIni.rs274ngc.remaps.map((remap) => remap.code).join(","), "M428,M429,M430");
|
||||
assert.equal(xyzacIni.hal.initialSets.some((set) => set.pin === "y-offset" && set.value === 20), true);
|
||||
|
||||
const derivedXyzac = applyIniConfigToProfile(xyzacTrtProfile, xyzacIni);
|
||||
assert.equal(derivedXyzac.machineName, xyzacIni.machineName);
|
||||
assert.equal(derivedXyzac.traj.coordinates, "XYZAC");
|
||||
assert.equal(derivedXyzac.axisLimits.X.min, -200);
|
||||
assert.equal(derivedXyzac.jointConfig.length, 5);
|
||||
assert.equal(derivedXyzac.linuxCncIniConfig.path.endsWith("xyzac-trt.ini"), true);
|
||||
|
||||
const xyzbcIni = parseLinuxCncIni(xyzbcIniText, {
|
||||
path: xyzbcTrtProfile.iniPath,
|
||||
profileId: xyzbcTrtProfile.id,
|
||||
});
|
||||
assert.equal(xyzbcIni.validation.ready, true);
|
||||
assert.equal(xyzbcIni.kinematics.name, "xyzbc-trt-kins");
|
||||
assert.equal(xyzbcIni.kinematicsModuleId, "xyzbc-trt");
|
||||
assert.equal(xyzbcIni.traj.coordinates, "XYZBC");
|
||||
assert.equal(xyzbcIni.axisLimits.B.max, 36000);
|
||||
assert.equal(xyzbcIni.jointConfig[3].axis, "B");
|
||||
assert.equal(xyzbcIni.kinematicsParameters.switchkinsTypes[1].webKinsType, "tcp-xyzbc");
|
||||
|
||||
console.log("linuxcnc_ini_runtime_smoke=ok");
|
||||
@@ -0,0 +1,80 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc-interpreter-runtime.js";
|
||||
|
||||
const runtime = await createLinuxCncInterpreterRuntime();
|
||||
const readiness = runtime.readiness();
|
||||
|
||||
assert.equal(runtime.apiName, "web-rtcp-5axis-linuxcnc-interpreter-runtime");
|
||||
assert.equal(runtime.loaded, true);
|
||||
assert.equal(runtime.sourceMode, "linuxcnc-interpreter-wasm");
|
||||
assert.equal(runtime.semanticBoundary, "linuxcnc_interpreter_wasm_canonical_events");
|
||||
assert.equal(readiness.loaded, true);
|
||||
assert.equal(readiness.runProgramReady, true);
|
||||
assert.equal(readiness.remapRuntimeReady, false);
|
||||
assert.equal(readiness.plannerRuntimeReady, true);
|
||||
assert.equal(readiness.plannerSemanticBoundary, "linuxcnc_tp_queue_runtime_timing_from_canonical_motion");
|
||||
|
||||
const programText = [
|
||||
"G90 G17",
|
||||
"G0 X0 Y0 Z0",
|
||||
"G1 X10 Y2 F100",
|
||||
"G1 X12 Y4 A5 C7",
|
||||
"M2",
|
||||
].join("\n");
|
||||
const execution = runtime.runProgram(programText);
|
||||
|
||||
assert.equal(execution.apiName, "web-rtcp-5axis-linuxcnc-interpreter-program-execution");
|
||||
assert.equal(execution.sourceMode, "linuxcnc-interpreter-wasm");
|
||||
assert.equal(execution.semanticBoundary, "linuxcnc_interpreter_wasm_canonical_events");
|
||||
assert.equal(execution.summary.ready, true);
|
||||
assert.equal(execution.summary.motionEventCount >= 3, true);
|
||||
assert.equal(execution.summary.canonicalEventCount > execution.summary.motionEventCount, true);
|
||||
assert.equal(execution.summary.motionTypes.includes("STRAIGHT_TRAVERSE"), true);
|
||||
assert.equal(execution.summary.motionTypes.includes("STRAIGHT_FEED"), true);
|
||||
assert.equal(execution.summary.finalAxes.x, 12);
|
||||
assert.equal(execution.summary.finalAxes.y, 4);
|
||||
assert.equal(execution.summary.finalAxes.a, 5);
|
||||
assert.equal(execution.summary.finalAxes.c, 7);
|
||||
assert.equal(execution.motion[2].feedRate, 100);
|
||||
assert.equal(execution.summary.remapRuntimeReady, false);
|
||||
assert.equal(execution.summary.plannerRuntimeReady, true);
|
||||
assert.equal(execution.plannerTiming.semanticBoundary, "linuxcnc_tp_queue_runtime_timing_from_canonical_motion");
|
||||
assert.equal(execution.plannerTiming.totalSeconds > 0, true);
|
||||
assert.equal(execution.plannerTiming.motionCount, execution.motion.length);
|
||||
assert.equal(execution.summary.fullLinuxCncProgramExecutionReady, false);
|
||||
|
||||
const switchkinsProgramText = [
|
||||
"G90 G17",
|
||||
"M428",
|
||||
"G0 X0 Y0 Z0 A0 C0",
|
||||
"G1 X10 Y2 Z-1 A15 C30 F120",
|
||||
"M429",
|
||||
"G1 X0 Y0 Z0 A0 C0 F120",
|
||||
"M2",
|
||||
].join("\n");
|
||||
const switchkinsExecution = runtime.runProgram(switchkinsProgramText);
|
||||
|
||||
assert.equal(switchkinsExecution.summary.ready, true);
|
||||
assert.equal(switchkinsExecution.summary.motionEventCount, 3);
|
||||
assert.equal(switchkinsExecution.summary.switchkinsEventCount, 2);
|
||||
assert.deepEqual(switchkinsExecution.summary.switchkinsCodes, ["M428", "M429"]);
|
||||
assert.equal(
|
||||
switchkinsExecution.summary.switchkinsRemapBoundary,
|
||||
"linuxcnc_switchkins_remap_mcode_preserved_web_runtime_applied",
|
||||
);
|
||||
assert.equal(switchkinsExecution.switchkinsEvents[0].switchkinsType, 1);
|
||||
assert.equal(switchkinsExecution.switchkinsEvents[1].switchkinsType, 0);
|
||||
assert.equal(switchkinsExecution.motion[0].kinsType, "tcp");
|
||||
assert.equal(switchkinsExecution.motion[1].axes.a, 15);
|
||||
assert.equal(switchkinsExecution.motion[1].axes.c, 30);
|
||||
assert.equal(switchkinsExecution.motion[1].switchkinsCode, "M428");
|
||||
assert.equal(switchkinsExecution.motion[2].kinsType, "identity");
|
||||
assert.equal(switchkinsExecution.motion[2].switchkinsCode, "M429");
|
||||
|
||||
assert.equal(switchkinsExecution.summary.remapRuntimeReady, false);
|
||||
assert.equal(switchkinsExecution.summary.plannerRuntimeReady, true);
|
||||
assert.equal(switchkinsExecution.plannerTiming.totalSeconds > 0, true);
|
||||
assert.equal(switchkinsExecution.summary.fullLinuxCncProgramExecutionReady, false);
|
||||
|
||||
console.log("linuxcnc_interpreter_runtime_smoke=ok");
|
||||
@@ -11,8 +11,10 @@ assert.equal(runtime.moduleId, "xyzac-trt");
|
||||
assert.equal(runtime.loaded, true);
|
||||
assert.equal(runtime.sourceMode, "source-derived-kinematics-wasm");
|
||||
assert.equal(runtime.semanticBoundary, "linuxcnc_kinematics_wasm_c_abi");
|
||||
assert.equal(runtime.executionContext, "direct");
|
||||
assert.equal(runtime.wasmFile, "linuxcnc_xyzac_trt_kinematics.wasm");
|
||||
assert.equal(readiness.loaded, true);
|
||||
assert.equal(readiness.executionContext, "direct");
|
||||
assert.equal(readiness.supportedModules.includes("xyzac-trt"), true);
|
||||
assert.equal(runtime.switchRc, 0);
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { createMemorySessionStorage } from "../../app/src/runtime/five-axis-session.js";
|
||||
import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc-interpreter-runtime.js";
|
||||
import {
|
||||
createMachineFileStagingPlan,
|
||||
selectMachineFileProgram,
|
||||
stageProfileMachineFiles,
|
||||
} from "../../app/src/runtime/linuxcnc-machine-file-staging.js";
|
||||
import { getFiveAxisProfile } from "../../app/src/profiles/index.js";
|
||||
import { createSimulationStore } from "../../app/src/state/store.js";
|
||||
|
||||
async function waitForMachineFileExecution(store) {
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
const state = store.getState();
|
||||
if (!state.interpreterExecutionPending && state.machineFileExecution) {
|
||||
return state;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
return store.getState();
|
||||
}
|
||||
|
||||
const profile = getFiveAxisProfile("xyzac-trt");
|
||||
const storage = createMemorySessionStorage();
|
||||
const plan = await createMachineFileStagingPlan({ profile });
|
||||
|
||||
assert.equal(plan.apiName, "web-rtcp-5axis-machine-file-staging-plan");
|
||||
assert.equal(plan.profileId, "xyzac-trt");
|
||||
assert.equal(plan.semanticBoundary, "linuxcnc_sim_config_file_staging_plan_only");
|
||||
assert.equal(plan.files.some((file) => file.sourceRel.endsWith("xyzac-trt.ini")), true);
|
||||
assert.equal(plan.files.some((file) => file.sourceRel.endsWith("xyzac-trt.tbl")), true);
|
||||
assert.equal(plan.files.some((file) => file.sourceRel.endsWith("remap_subs/428remap.ngc")), true);
|
||||
assert.equal(plan.files.some((file) => file.sourceRel.endsWith("remap_subs/429remap.ngc")), true);
|
||||
assert.equal(plan.files.some((file) => file.sourceRel.endsWith("demos/xyzac_switchkins.ngc")), true);
|
||||
assert.equal(plan.summary.remapFileCount >= 3, true);
|
||||
assert.equal(plan.summary.demoFileCount >= 1, true);
|
||||
|
||||
const staged = await stageProfileMachineFiles(profile, { storage });
|
||||
assert.equal(staged.save.apiName, "web-rtcp-5axis-machine-file-staging-save");
|
||||
assert.equal(staged.save.status, "saved");
|
||||
assert.equal(staged.save.fileCount, staged.plan.files.length);
|
||||
assert.equal(staged.save.semanticBoundary, "opfs_machine_file_text_staging_only");
|
||||
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.files.every((file) => file.opfsPath.startsWith("web-rtcp-5axis-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/,
|
||||
);
|
||||
|
||||
const iniOpfsPath = staged.save.files.find((file) => file.sourceRel.endsWith("xyzac-trt.ini")).opfsPath;
|
||||
assert.equal(storage.files.get(iniOpfsPath).includes("KINEMATICS = xyzac-trt-kins"), true);
|
||||
|
||||
const store = createSimulationStore();
|
||||
store.dispatch({
|
||||
type: "ATTACH_INTERPRETER_RUNTIME",
|
||||
runtime: await createLinuxCncInterpreterRuntime(),
|
||||
});
|
||||
const storeStage = await store.stageMachineFiles({ storage });
|
||||
const state = store.getState();
|
||||
assert.equal(storeStage.save.fileCount, staged.save.fileCount);
|
||||
assert.equal(state.machineFileStaging.status, "staged");
|
||||
assert.equal(state.machineFileStaging.profileId, "xyzac-trt");
|
||||
assert.equal(state.machineFileStaging.fileCount, staged.save.fileCount);
|
||||
assert.equal(state.machineFileStaging.save.summary.kinds.remap >= 3, true);
|
||||
assert.equal(state.machineFileStaging.gcodeSources.length >= 4, true);
|
||||
assert.equal(state.machineFileStaging.selectedGcodeSourceRel, null);
|
||||
|
||||
store.dispatch({ type: "RUN_MACHINE_FILE_PROGRAM" });
|
||||
assert.equal(
|
||||
store.getState().operatorMessage,
|
||||
"machine-file run blocked: select a LinuxCNC source-directory 5-axis G-code program",
|
||||
);
|
||||
|
||||
store.dispatch({
|
||||
type: "LOAD_LINUXCNC_GCODE_SOURCE",
|
||||
sourceRel: "operator-demo.ngc",
|
||||
});
|
||||
assert.equal(
|
||||
store.getState().operatorMessage,
|
||||
"LinuxCNC G-code source not staged: operator-demo.ngc",
|
||||
);
|
||||
|
||||
store.dispatch({
|
||||
type: "LOAD_LINUXCNC_GCODE_SOURCE",
|
||||
sourceRel: "configs/sim/axis/vismach/5axis/table-rotary-tilting/demos/impeller-7bl-xyzac.ngc",
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
const sourceState = store.getState();
|
||||
assert.equal(sourceState.programSource, "linuxcnc-vendored-5axis-gcode");
|
||||
assert.equal(sourceState.activeProgram.endsWith("impeller-7bl-xyzac.ngc"), true);
|
||||
assert.equal(sourceState.programLines[0].includes("Impeller 5-axis"), true);
|
||||
assert.equal(sourceState.machineFileStaging.selectedGcodeSourceRel.endsWith("impeller-7bl-xyzac.ngc"), true);
|
||||
assert.equal(sourceState.machineFileStaging.plan.wasmProgramPath.endsWith("/demos/impeller-7bl-xyzac.ngc"), true);
|
||||
|
||||
store.dispatch({ type: "RUN_MACHINE_FILE_PROGRAM" });
|
||||
const runState = await waitForMachineFileExecution(store);
|
||||
assert.equal(runState.machineFileExecution.sourceMode, "linuxcnc-machine-file-remap-wasm");
|
||||
assert.equal(runState.machineFileExecution.semanticBoundary, "linuxcnc_fiveaxis_remap_wasm_machine_file_execution");
|
||||
assert.equal(runState.machineFileExecution.summary.machineFileExecutionReady, true);
|
||||
assert.equal(runState.machineFileExecution.summary.remapRuntimeReady, true);
|
||||
assert.equal(runState.machineFileExecution.summary.plannerRuntimeReady, false);
|
||||
assert.equal(runState.machineFileExecution.summary.fullLinuxCncProgramExecutionReady, false);
|
||||
assert.equal(runState.machineFileExecution.resultText.includes("fiveaxis_ini_open=1"), true);
|
||||
assert.equal(runState.machineFileExecution.resultText.includes("fiveaxis_remaps_ready=1"), true);
|
||||
assert.equal(runState.machineFileExecution.resultText.includes("fiveaxis_file_reached_exit=1"), true);
|
||||
assert.equal(runState.machineFileExecution.machineFilePlan.selectedProgramFilename, "impeller-7bl-xyzac.ngc");
|
||||
assert.equal(runState.machineFileExecution.machineFilePlan.wasmProgramPath.endsWith("/demos/impeller-7bl-xyzac.ngc"), true);
|
||||
assert.equal(runState.fullExecutionBoundary.apiName, "web-rtcp-5axis-full-linuxcnc-execution-boundary");
|
||||
assert.equal(runState.fullExecutionBoundary.machineFileBackedRemapReady, true);
|
||||
assert.equal(runState.fullExecutionBoundary.remapRuntimeReady, true);
|
||||
assert.equal(runState.fullExecutionBoundary.halSwitchkinsEvidenceReady, true);
|
||||
assert.equal(runState.fullExecutionBoundary.plannerRuntimeReady, false);
|
||||
assert.equal(runState.fullExecutionBoundary.nativeTaskReady, false);
|
||||
assert.equal(runState.fullExecutionBoundary.nativeHalSyncReady, false);
|
||||
assert.equal(runState.fullExecutionBoundary.fullLinuxCncProgramExecutionReady, false);
|
||||
assert.equal(runState.fullExecutionBoundary.promotionAllowed, false);
|
||||
assert.equal(
|
||||
runState.fullExecutionBoundary.semanticBoundary,
|
||||
"linuxcnc_machine_file_remap_ready_planner_task_hal_blocked",
|
||||
);
|
||||
assert.equal(runState.fullExecutionBoundary.satisfied.includes("fiveaxis-remap-machine-file-run"), true);
|
||||
assert.equal(runState.fullExecutionBoundary.satisfied.includes("switchkins-hal-bridge-evidence"), true);
|
||||
assert.equal(runState.fullExecutionBoundary.blockers.some((blocker) => blocker.includes("trajectory planner")), true);
|
||||
|
||||
console.log("machine_file_staging_smoke=ok");
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { xyzacTrtProfile } from "../../app/src/profiles/xyzac-trt.js";
|
||||
import { getFiveAxisProfile, fiveAxisProfiles } from "../../app/src/profiles/index.js";
|
||||
import { createPyvcpHalBindingSummary, xyzacTrtPyvcpPanelSchema } from "../../app/src/panel-schema/xyzac-trt-pyvcp.js";
|
||||
import { createLinuxCncBoundaryAdapter, createLinuxCncBoundaryReadiness } from "../../app/src/runtime/linuxcnc-boundary-adapter.js";
|
||||
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
|
||||
@@ -29,6 +30,20 @@ assert.equal(xyzacTrtProfile.halPins.includes("motion.switchkins-type"), true);
|
||||
assert.equal(xyzacTrtProfile.halPins.includes("xyzac-trt-kins.tool-offset"), true);
|
||||
assert.equal(xyzacTrtProfile.promotionAllowed, false);
|
||||
assert.equal(xyzacTrtProfile.linuxCncKinematicsReady, false);
|
||||
assert.deepEqual(fiveAxisProfiles.map(({ id }) => id), ["xyzac-trt", "xyzbc-trt"]);
|
||||
|
||||
const xyzbcTrtProfile = getFiveAxisProfile("xyzbc-trt");
|
||||
assert.equal(xyzbcTrtProfile.id, "xyzbc-trt");
|
||||
assert.equal(xyzbcTrtProfile.iniPath.endsWith("xyzbc-trt.ini"), true);
|
||||
assert.deepEqual(xyzbcTrtProfile.coordinates, ["X", "Y", "Z", "B", "C"]);
|
||||
assert.equal(xyzbcTrtProfile.kinematics, "xyzbc-trt-kins");
|
||||
assert.equal(xyzbcTrtProfile.kinematicsModuleId, "xyzbc-trt");
|
||||
assert.equal(xyzbcTrtProfile.machineName, "sim-xyzbc-trt-kins (switchkins)");
|
||||
assert.equal(xyzbcTrtProfile.traj.coordinates, "XYZBC");
|
||||
assert.equal(xyzbcTrtProfile.axisLimits.B.max, 36000);
|
||||
assert.equal(xyzbcTrtProfile.hal.halcmd.feedbackNets.some((net) => net.target === "xyzbc-trt-gui.tilt-b"), true);
|
||||
assert.equal(xyzbcTrtProfile.halPins.includes("xyzbc-trt-kins.x-offset"), true);
|
||||
assert.equal(xyzbcTrtProfile.panelSchema.id, "xyzbc-trt-switchkins-pyvcp");
|
||||
|
||||
const sourceSummary = createProfileSourceReferenceSummary("xyzac-trt");
|
||||
assert.equal(sourceSummary.referenceCount >= 8, true);
|
||||
@@ -39,6 +54,11 @@ assert.equal(sourceSummary.semanticBoundary, "profile_source_map_only_not_runtim
|
||||
assert.ok(sourceSummary.references.some((reference) => reference.path.endsWith("xyzac-trt.ini")));
|
||||
assert.ok(sourceSummary.references.some((reference) => reference.path.endsWith("trtfuncs.c")));
|
||||
|
||||
const xyzbcSourceSummary = createProfileSourceReferenceSummary("xyzbc-trt");
|
||||
assert.equal(xyzbcSourceSummary.referenceCount >= 7, true);
|
||||
assert.ok(xyzbcSourceSummary.references.some((reference) => reference.path.endsWith("xyzbc-trt.ini")));
|
||||
assert.ok(xyzbcSourceSummary.references.some((reference) => reference.path.endsWith("xyzbc-trt-kins.c")));
|
||||
|
||||
const panelSummary = createPyvcpHalBindingSummary(xyzacTrtPyvcpPanelSchema);
|
||||
assert.equal(panelSummary.schemaId, "xyzac-trt-switchkins-pyvcp");
|
||||
assert.equal(panelSummary.controlCount, 5);
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { createLinuxCncInterpreterRuntime } from "../../app/src/runtime/linuxcnc-interpreter-runtime.js";
|
||||
import { createLinuxCncKinematicsRuntime } from "../../app/src/runtime/linuxcnc-kinematics-runtime.js";
|
||||
import { parseLinuxCncIni } from "../../app/src/runtime/linuxcnc-ini-runtime.js";
|
||||
import { buildRtcpFrame } from "../../app/src/runtime/rtcp-frame.js";
|
||||
import { createSimulationStore } from "../../app/src/state/store.js";
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
||||
async function waitForInterpreterExecution(store) {
|
||||
for (let attempt = 0; attempt < 20; attempt += 1) {
|
||||
const state = store.getState();
|
||||
if (!state.interpreterExecutionPending && state.programExecutionSourceMode === "linuxcnc-interpreter-wasm") {
|
||||
return state;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
return store.getState();
|
||||
}
|
||||
|
||||
const identityFrame = buildRtcpFrame({
|
||||
axisPose: { x: 43, y: -32.15, z: -11.306, a: 0, b: 0, c: 0 },
|
||||
@@ -34,6 +48,7 @@ assert.ok(Number.isFinite(tcpFrame.toolAxisVector.z));
|
||||
assert.equal(tcpFrame.jointPose.length, 5);
|
||||
|
||||
const runtime = await createLinuxCncKinematicsRuntime({ moduleId: "xyzac-trt" });
|
||||
const interpreterRuntime = await createLinuxCncInterpreterRuntime();
|
||||
const linuxCncKinematicsResult = runtime.frameForJoints([10, 20, 30, 25, 40]);
|
||||
const linuxCncFrame = buildRtcpFrame({
|
||||
axisPose: { x: 10, y: 20, z: 30, a: 25, b: 0, c: 40 },
|
||||
@@ -65,6 +80,37 @@ assert.equal(fixtureInitialStore.getState().linuxCncBoundaryAdapter.profileSumma
|
||||
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryAdapter.profileSummary.coordinates, "XYZAC");
|
||||
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryReadiness.ready, false);
|
||||
assert.equal(fixtureInitialStore.getState().linuxCncBoundaryReadiness.promotionAllowed, false);
|
||||
assert.equal(fixtureInitialStore.getState().fullExecutionBoundary.fullLinuxCncProgramExecutionReady, false);
|
||||
assert.equal(fixtureInitialStore.getState().fullExecutionBoundary.promotionAllowed, false);
|
||||
assert.equal(fixtureInitialStore.getState().fullExecutionBoundary.phase, "blocked");
|
||||
assert.equal(fixtureInitialStore.getState().linuxCncTaskPolicy.semanticBoundary, "linuxcnc_task_state_mode_command_gate");
|
||||
assert.equal(
|
||||
fixtureInitialStore.getState().linuxCncTaskPolicy.sourceReferences.some((reference) => reference.path === "linuxcnc/src/emc/task/emctaskmain.cc"),
|
||||
true,
|
||||
);
|
||||
assert.equal(fixtureInitialStore.getState().linuxCncTaskPolicy.canRunAuto, false);
|
||||
|
||||
const xyzacIniText = await readFile(
|
||||
new URL("../../../wasm-port/vendor/linuxcnc/configs/sim/axis/vismach/5axis/table-rotary-tilting/xyzac-trt.ini", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const xyzacIniConfig = parseLinuxCncIni(xyzacIniText, {
|
||||
path: fixtureInitialStore.getState().profile.iniPath,
|
||||
profileId: fixtureInitialStore.getState().machineProfile,
|
||||
});
|
||||
fixtureInitialStore.dispatch({ type: "ATTACH_INI_CONFIG", iniConfig: xyzacIniConfig });
|
||||
assert.equal(fixtureInitialStore.getState().iniConfigReadiness.loaded, true);
|
||||
assert.equal(fixtureInitialStore.getState().iniConfigReadiness.ready, true);
|
||||
assert.equal(fixtureInitialStore.getState().profile.axisLimits.X.max, 200);
|
||||
fixtureInitialStore.dispatch({ type: "TOGGLE_POWER" });
|
||||
fixtureInitialStore.dispatch({ type: "HOME" });
|
||||
fixtureInitialStore.dispatch({ type: "SET_MODE", mode: "mdi" });
|
||||
fixtureInitialStore.dispatch({ type: "RUN_MDI", command: "G90 X999 Y-999 Z999 A999 C99999" });
|
||||
assert.equal(fixtureInitialStore.getState().axisPose.x, 200);
|
||||
assert.equal(fixtureInitialStore.getState().axisPose.y, -100);
|
||||
assert.equal(fixtureInitialStore.getState().axisPose.z, 120);
|
||||
assert.equal(fixtureInitialStore.getState().axisPose.a, 50);
|
||||
assert.equal(fixtureInitialStore.getState().axisPose.c, 36000);
|
||||
|
||||
const kinematicsStore = createSimulationStore();
|
||||
kinematicsStore.dispatch({ type: "ATTACH_KINEMATICS_RUNTIME", runtime });
|
||||
@@ -74,6 +120,9 @@ assert.equal(kinematicsState.linuxCncBoundaryAdapter.interpreterRuntimeReady, fa
|
||||
assert.equal(kinematicsState.linuxCncBoundaryReadiness.ready, true);
|
||||
assert.equal(kinematicsState.linuxCncBoundaryReadiness.fullLinuxCncProgramExecutionReady, false);
|
||||
assert.equal(kinematicsState.linuxCncBoundaryReadiness.missing.includes("interpreter/remap runtime"), true);
|
||||
assert.equal(kinematicsState.fullExecutionBoundary.satisfied.includes("linuxcnc-kinematics-wasm"), true);
|
||||
assert.equal(kinematicsState.fullExecutionBoundary.missing.includes("linuxcnc interpreter WASM runtime"), true);
|
||||
assert.equal(kinematicsState.kinematicsExecutionContext, "direct");
|
||||
assert.equal(kinematicsState.rtcpFrame.sourceMode, "source-derived-kinematics-wasm");
|
||||
assert.equal(kinematicsState.rtcpFrame.semanticBoundary, "linuxcnc_kinematics_wasm_c_abi");
|
||||
assert.equal(kinematicsState.rtcpFrame.readiness.linuxCncKinematicsReady, true);
|
||||
@@ -86,27 +135,159 @@ assert.equal(kinematicsState.lastKinematicsResult.moduleId, "xyzac-trt");
|
||||
assert.equal(kinematicsState.dro.tcpX, kinematicsState.rtcpFrame.tcpPose.x);
|
||||
|
||||
kinematicsStore.dispatch({ type: "TOGGLE_POWER" });
|
||||
kinematicsStore.dispatch({ type: "HOME" });
|
||||
kinematicsStore.dispatch({ type: "SET_MODE", mode: "auto" });
|
||||
kinematicsStore.dispatch({ type: "STEP" });
|
||||
kinematicsState = kinematicsStore.getState();
|
||||
assert.equal(kinematicsState.runState, "stepping");
|
||||
assert.equal(kinematicsState.rtcpFrame.sourceMode, "source-derived-kinematics-wasm");
|
||||
assert.equal(kinematicsState.rtcpFrame.readiness.fullLinuxCncProgramExecutionReady, false);
|
||||
|
||||
kinematicsStore.dispatch({ type: "ATTACH_INTERPRETER_RUNTIME", runtime: interpreterRuntime });
|
||||
kinematicsStore.dispatch({
|
||||
type: "LOAD_PROGRAM",
|
||||
filename: "linuxcnc-canonical-demo.ngc",
|
||||
content: [
|
||||
"G90 G17",
|
||||
"G0 X0 Y0 Z0",
|
||||
"G1 X10 Y2 F100",
|
||||
"G1 X12 Y4 A5 C7",
|
||||
"M2",
|
||||
].join("\n"),
|
||||
});
|
||||
kinematicsState = await waitForInterpreterExecution(kinematicsStore);
|
||||
assert.equal(kinematicsState.linuxCncBoundaryAdapter.linuxCncInterpreterReady, true);
|
||||
assert.equal(kinematicsState.linuxCncBoundaryReadiness.linuxCncInterpreterReady, true);
|
||||
assert.equal(kinematicsState.programExecutionSourceMode, "linuxcnc-interpreter-wasm");
|
||||
assert.equal(kinematicsState.programExecution.summary.motionEventCount >= 3, true);
|
||||
assert.equal(kinematicsState.programExecutionTiming.motionCount >= 3, true);
|
||||
assert.equal(kinematicsState.programExecutionTiming.sampleCount >= 3, true);
|
||||
assert.equal(kinematicsState.programExecutionTiming.totalSeconds > 0, true);
|
||||
assert.equal(kinematicsState.programExecutionTiming.semanticBoundary, "linuxcnc_tp_queue_runtime_timing_from_canonical_motion");
|
||||
assert.equal(kinematicsState.programExecution.summary.plannerRuntimeReady, true);
|
||||
assert.equal(kinematicsState.fullExecutionBoundary.plannerRuntimeReady, true);
|
||||
assert.equal(kinematicsState.programExecution.summary.fullLinuxCncProgramExecutionReady, false);
|
||||
assert.equal(kinematicsState.fullExecutionBoundary.readyForUiSimulation, true);
|
||||
assert.equal(kinematicsState.fullExecutionBoundary.machineFileBackedRemapReady, false);
|
||||
assert.equal(kinematicsState.fullExecutionBoundary.fullLinuxCncProgramExecutionReady, false);
|
||||
assert.equal(
|
||||
kinematicsState.fullExecutionBoundary.semanticBoundary,
|
||||
"linuxcnc_interpreter_canonical_ready_planner_task_hal_blocked",
|
||||
);
|
||||
|
||||
kinematicsStore.dispatch({ type: "STEP" });
|
||||
kinematicsState = kinematicsStore.getState();
|
||||
assert.equal(kinematicsState.programExecutionMotionIndex, 1);
|
||||
assert.equal(kinematicsState.activeLine, kinematicsState.programExecution.motion[1].line);
|
||||
assert.equal(kinematicsState.programRuntimeFeedback.sourceMode, "linuxcnc-tp-runtime-sample");
|
||||
assert.equal(kinematicsState.programRuntimeFeedback.semanticBoundary, "linuxcnc_tp_run_cycle_feedback_without_hardware");
|
||||
assert.equal(kinematicsState.axisPose.x, kinematicsState.programRuntimeFeedback.axisPose.x);
|
||||
assert.notEqual(kinematicsState.axisPose.x, kinematicsState.programExecution.motion[1].axes.x);
|
||||
assert.equal(kinematicsState.programRuntimeFeedback.queueDepth >= 0, true);
|
||||
assert.equal(kinematicsState.programElapsedSeconds > 0, true);
|
||||
assert.equal(kinematicsState.programRemainingSeconds >= 0, true);
|
||||
assert.equal(kinematicsState.feed.currentVelocity > 0, true);
|
||||
|
||||
kinematicsStore.dispatch({
|
||||
type: "LOAD_PROGRAM",
|
||||
filename: "linuxcnc-switchkins-rtcp-demo.ngc",
|
||||
content: [
|
||||
"G90 G17",
|
||||
"M428",
|
||||
"G0 X0 Y0 Z0 A0 C0",
|
||||
"G1 X10 Y2 Z-1 A15 C30 F120",
|
||||
"M429",
|
||||
"G1 X0 Y0 Z0 A0 C0 F120",
|
||||
"M2",
|
||||
].join("\n"),
|
||||
});
|
||||
kinematicsState = await waitForInterpreterExecution(kinematicsStore);
|
||||
assert.equal(kinematicsState.programExecutionSourceMode, "linuxcnc-interpreter-wasm");
|
||||
assert.equal(kinematicsState.programExecution.summary.switchkinsEventCount, 2);
|
||||
assert.equal(kinematicsState.programExecution.motion[0].switchkinsType, 1);
|
||||
assert.equal(kinematicsState.kinsType, "tcp-xyzac");
|
||||
assert.equal(kinematicsState.rtcpState, "on");
|
||||
assert.equal(kinematicsState.rtcpFrame.kinematicsSwitchkinsType, 1);
|
||||
|
||||
kinematicsStore.dispatch({ type: "STEP" });
|
||||
kinematicsState = kinematicsStore.getState();
|
||||
assert.equal(kinematicsState.programExecutionMotionIndex, 1);
|
||||
assert.equal(kinematicsState.programRuntimeFeedback.semanticBoundary, "linuxcnc_tp_run_cycle_feedback_without_hardware");
|
||||
assert.equal(kinematicsState.axisPose.a > 0 && kinematicsState.axisPose.a < 15, true);
|
||||
assert.equal(kinematicsState.axisPose.c > 0 && kinematicsState.axisPose.c < 30, true);
|
||||
assert.equal(kinematicsState.programRuntimeFeedback.distanceToGo > 0, true);
|
||||
assert.equal(kinematicsState.kinsType, "tcp-xyzac");
|
||||
assert.equal(kinematicsState.rtcpState, "on");
|
||||
assert.equal(kinematicsState.rtcpFrame.kinematicsSwitchkinsType, 1);
|
||||
const switchkinsStepSampleIndex = kinematicsState.programExecutionSampleIndex;
|
||||
|
||||
kinematicsStore.dispatch({ type: "RESUME" });
|
||||
kinematicsStore.dispatch({ type: "RUN" });
|
||||
kinematicsState = kinematicsStore.getState();
|
||||
assert.equal(kinematicsState.programExecutionSampleIndex > switchkinsStepSampleIndex, true);
|
||||
assert.equal(kinematicsState.programExecutionMotionIndex, 1);
|
||||
assert.equal(kinematicsState.programRuntimeFeedback.semanticBoundary, "linuxcnc_tp_run_cycle_feedback_without_hardware");
|
||||
assert.equal(kinematicsState.programRuntimeFeedback.distanceToGo > 0, true);
|
||||
assert.equal(kinematicsState.kinsType, "tcp-xyzac");
|
||||
assert.equal(kinematicsState.rtcpState, "on");
|
||||
assert.equal(kinematicsState.rtcpFrame.kinematicsSwitchkinsType, 1);
|
||||
|
||||
kinematicsStore.dispatch({ type: "PAUSE" });
|
||||
kinematicsState = kinematicsStore.getState();
|
||||
assert.equal(kinematicsState.runState, "paused");
|
||||
assert.equal(kinematicsState.machine.interpState, "paused");
|
||||
assert.equal(kinematicsState.machine.taskPaused, true);
|
||||
|
||||
kinematicsStore.dispatch({ type: "RUN" });
|
||||
kinematicsState = kinematicsStore.getState();
|
||||
assert.equal(kinematicsState.operatorMessage, "run blocked: resume paused program first");
|
||||
assert.equal(kinematicsState.runState, "paused");
|
||||
|
||||
kinematicsStore.dispatch({ type: "RESUME" });
|
||||
kinematicsState = kinematicsStore.getState();
|
||||
assert.equal(kinematicsState.runState, "running");
|
||||
assert.equal(kinematicsState.machine.interpState, "reading");
|
||||
assert.equal(kinematicsState.machine.taskPaused, false);
|
||||
|
||||
kinematicsStore.dispatch({ type: "STOP" });
|
||||
kinematicsState = kinematicsStore.getState();
|
||||
assert.equal(kinematicsState.runState, "stopped");
|
||||
assert.equal(kinematicsState.machine.interpState, "idle");
|
||||
|
||||
kinematicsStore.dispatch({ type: "SET_FRAME_SOURCE", sourceMode: "fixture-ui-only" });
|
||||
kinematicsState = kinematicsStore.getState();
|
||||
assert.equal(kinematicsState.rtcpFrame.sourceMode, "fixture-ui-only");
|
||||
assert.equal(kinematicsState.rtcpFrame.readiness.linuxCncKinematicsReady, false);
|
||||
|
||||
const store = createSimulationStore();
|
||||
let state;
|
||||
assert.equal(store.getState().availableProfiles.length, 2);
|
||||
store.dispatch({ type: "SET_PROFILE", profileId: "xyzbc-trt" });
|
||||
assert.equal(store.getState().machineProfile, "xyzbc-trt");
|
||||
assert.equal(store.getState().profile.traj.coordinates, "XYZBC");
|
||||
const xyzbcRuntime = await createLinuxCncKinematicsRuntime({ moduleId: "xyzbc-trt" });
|
||||
store.dispatch({ type: "ATTACH_KINEMATICS_RUNTIME", runtime: xyzbcRuntime });
|
||||
store.dispatch({ type: "SET_RTCP", enabled: true });
|
||||
state = store.getState();
|
||||
assert.equal(state.kinsType, "tcp-xyzbc");
|
||||
assert.equal(state.rtcpFrame.kinematicsModuleId, "xyzbc-trt");
|
||||
assert.equal(state.rtcpFrame.jointPose[3].axis, "B");
|
||||
|
||||
store.dispatch({ type: "SET_PROFILE", profileId: "xyzac-trt" });
|
||||
store.dispatch({ type: "RUN" });
|
||||
assert.equal(store.getState().activeLine, 501);
|
||||
assert.equal(store.getState().operatorMessage, "run blocked: power or estop state");
|
||||
assert.equal(store.getState().operatorMessage, "run blocked: machine must be on");
|
||||
|
||||
store.dispatch({ type: "TOGGLE_POWER" });
|
||||
assert.equal(store.getState().machine.powerOn, true);
|
||||
store.dispatch({ type: "HOME" });
|
||||
assert.equal(store.getState().machine.allHomed, true);
|
||||
store.dispatch({ type: "SET_MODE", mode: "auto" });
|
||||
assert.equal(store.getState().linuxCncTaskPolicy.canRunAuto, true);
|
||||
assert.equal(store.getState().linuxCncTaskPolicy.canExecuteMdi, false);
|
||||
|
||||
store.dispatch({ type: "SET_RTCP", enabled: true });
|
||||
let state = store.getState();
|
||||
state = store.getState();
|
||||
assert.equal(state.rtcpState, "on");
|
||||
assert.equal(state.kinsType, "tcp-xyzac");
|
||||
assert.equal(state.rtcpFrame.readiness.linuxCncKinematicsReady, false);
|
||||
@@ -120,24 +301,55 @@ assert.equal(state.activeLine, previousLine + 1);
|
||||
assert.equal(state.runState, "stepping");
|
||||
assert.notEqual(state.tcpPose.x, previousTcpX);
|
||||
|
||||
store.dispatch({ type: "STOP" });
|
||||
store.dispatch({ type: "SET_MODE", mode: "manual" });
|
||||
store.dispatch({ type: "JOG", axis: "x", direction: 1, increment: 2 });
|
||||
state = store.getState();
|
||||
assert.equal(state.machine.mode, "jog");
|
||||
assert.equal(state.machine.mode, "manual");
|
||||
assert.equal(state.runState, "jogging");
|
||||
assert.equal(Math.round(state.axisPose.x), Math.round(state.rtcpFrame.axisPose.x));
|
||||
|
||||
store.dispatch({ type: "SET_MODE", mode: "mdi" });
|
||||
store.dispatch({ type: "RUN_MDI", command: "G0 X1" });
|
||||
state = store.getState();
|
||||
assert.equal(state.machine.mode, "mdi");
|
||||
assert.equal(state.machine.mdiCommand, "G0 X1");
|
||||
assert.equal(state.runState, "mdi");
|
||||
assert.equal(state.axisPose.x, 1);
|
||||
assert.equal(state.programSource, "operator-mdi");
|
||||
assert.equal(state.programLines[0], "G0 X1");
|
||||
assert.equal(state.mdiHistory[0], "G0 X1");
|
||||
|
||||
const mdiRelativeBase = store.getState().axisPose;
|
||||
store.dispatch({ type: "RUN_MDI", command: "G91 X2 Y-3 F1200 M3 S2400 M8" });
|
||||
state = store.getState();
|
||||
assert.equal(state.machine.mdiDistanceMode, "relative");
|
||||
assert.equal(state.axisPose.x, mdiRelativeBase.x + 2);
|
||||
assert.equal(state.axisPose.y, mdiRelativeBase.y - 3);
|
||||
assert.equal(state.feed.feedRate, 1200);
|
||||
assert.equal(state.spindle.enabled, true);
|
||||
assert.equal(state.spindle.rpm, 2400);
|
||||
assert.equal(state.coolant.flood, true);
|
||||
|
||||
store.dispatch({ type: "RUN_MDI", command: "M428" });
|
||||
state = store.getState();
|
||||
assert.equal(state.kinsType, "tcp-xyzac");
|
||||
assert.equal(state.rtcpState, "on");
|
||||
|
||||
store.dispatch({ type: "RUN_MDI", command: "M429 M9 M5" });
|
||||
state = store.getState();
|
||||
assert.equal(state.kinsType, "identity");
|
||||
assert.equal(state.rtcpState, "off");
|
||||
assert.equal(state.coolant.flood, false);
|
||||
assert.equal(state.coolant.mist, false);
|
||||
assert.equal(state.spindle.enabled, false);
|
||||
|
||||
store.dispatch({
|
||||
type: "LOAD_PROGRAM",
|
||||
filename: "operator-demo.ngc",
|
||||
content: "G0 X0 Y0\nG1 X10 F100\nM30\n",
|
||||
});
|
||||
state = store.getState();
|
||||
state = await waitForInterpreterExecution(store);
|
||||
assert.equal(state.activeProgram, "operator-demo.ngc");
|
||||
assert.equal(state.programSource, "operator-file");
|
||||
assert.equal(state.programStartLine, 1);
|
||||
@@ -169,13 +381,15 @@ store.dispatch({ type: "ADJUST_SPINDLE_OVERRIDE", delta: 10 });
|
||||
state = store.getState();
|
||||
assert.equal(state.spindle.override, 110);
|
||||
|
||||
const floodBeforeToggle = store.getState().coolant.flood;
|
||||
store.dispatch({ type: "TOGGLE_COOLANT", kind: "flood" });
|
||||
state = store.getState();
|
||||
assert.equal(state.coolant.flood, false);
|
||||
assert.equal(state.coolant.flood, !floodBeforeToggle);
|
||||
|
||||
const mistBeforeToggle = store.getState().coolant.mist;
|
||||
store.dispatch({ type: "TOGGLE_COOLANT", kind: "mist" });
|
||||
state = store.getState();
|
||||
assert.equal(state.coolant.mist, true);
|
||||
assert.equal(state.coolant.mist, !mistBeforeToggle);
|
||||
|
||||
store.dispatch({ type: "SET_VIEW", view: "x" });
|
||||
state = store.getState();
|
||||
@@ -195,6 +409,7 @@ store.dispatch({ type: "TOGGLE_FULLSCREEN" });
|
||||
state = store.getState();
|
||||
assert.equal(state.preview.fullscreen, true);
|
||||
|
||||
store.dispatch({ type: "SET_MODE", mode: "manual" });
|
||||
store.dispatch({ type: "HOME" });
|
||||
state = store.getState();
|
||||
assert.equal(state.axisPose.x, 43);
|
||||
@@ -209,6 +424,8 @@ assert.equal(state.runState, "estopped");
|
||||
store.dispatch({ type: "RESET" });
|
||||
state = store.getState();
|
||||
assert.equal(state.machine.estopActive, false);
|
||||
assert.equal(state.operatorMessage, "machine reset complete");
|
||||
assert.equal(state.machine.powerOn, false);
|
||||
assert.equal(state.machine.taskState, "estop-reset");
|
||||
assert.equal(state.operatorMessage, "estop reset; machine off");
|
||||
|
||||
console.log("rtcp_store_smoke=ok");
|
||||
|
||||
Reference in New Issue
Block a user