states fixing idle issue?
This commit is contained in:
@@ -42,7 +42,7 @@
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
|
||||
<script src="../../lib/WallPlotter.js?v=2.2.5"></script>
|
||||
<script src="../../lib/WallPlotter.js?v=2.3.0"></script>
|
||||
<script src="sketch.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
}
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
|
||||
<script src="../../lib/WallPlotter.js?v=2.2.5"></script>
|
||||
<script src="../../lib/WallPlotter.js?v=2.3.0"></script>
|
||||
<script src="sketch.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ Returns estimated duration in **milliseconds** for the current path buffer, or f
|
||||
|
||||
### `stopPath()` / `stopPlot()`
|
||||
|
||||
`stopPath()` aborts the current `runPath` and unblocks any wait for Marlin `ok`.
|
||||
`stopPath()` aborts the current `runPath` after the in-flight command gets its `ok` (it does not cancel that wait — that was dropping the next `ok`).
|
||||
|
||||
`stopPlot()` first press is the same as **Pause**: finish the current stroke, then **Resume** continues. Second press stops the browser from sending more moves. The **in-flight** `G1` still runs — this firmware does not have `EMERGENCY_PARSER`, so `M410` cannot interrupt motion (and on a polargraph it desyncs belts). Unplug power for a hard stop.
|
||||
|
||||
|
||||
+153
-180
@@ -18,7 +18,7 @@ class WallPlotter {
|
||||
// Static constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static VERSION = '2.2.5';
|
||||
static VERSION = '2.3.0';
|
||||
static MIN_FEED_MM_MIN = 1;
|
||||
static MAX_FEED_MM_MIN = 10000;
|
||||
/** Offline overlay size when not connected (mm) — 150×150 cm centred on origin */
|
||||
@@ -82,8 +82,8 @@ class WallPlotter {
|
||||
this.previewMinDelayMs = options.previewMinDelayMs ?? 30;
|
||||
this.previewFrameMs = options.previewFrameMs ?? 16;
|
||||
|
||||
// --- Live machine state ---
|
||||
this.connected = false;
|
||||
// --- Link: disconnected | connecting | connected ---
|
||||
this._link = 'disconnected';
|
||||
this.homeSet = false;
|
||||
this.isPenDown = false;
|
||||
this.x = 0;
|
||||
@@ -91,35 +91,28 @@ class WallPlotter {
|
||||
this.statusText = 'Disconnected';
|
||||
this.logLines = [];
|
||||
|
||||
// --- Path / plot progress ---
|
||||
// --- Job: idle | running | paused | aborting ---
|
||||
this._job = 'idle';
|
||||
this._path = [];
|
||||
this.previewPath = [];
|
||||
this.isPlotting = false;
|
||||
this.isPaused = false;
|
||||
this.plotProgress = 0;
|
||||
this._plotTestOnly = false;
|
||||
this._pathAbort = false;
|
||||
this._pathRunId = 0;
|
||||
this._pendingAfterAbort = null;
|
||||
this._pathLoopActive = false;
|
||||
this._plotStartedAt = 0;
|
||||
this._plotEstimateMs = 0;
|
||||
this._pausedAccumMs = 0;
|
||||
this._pauseStartedAt = 0;
|
||||
this._statusBeforePause = null;
|
||||
this._statusBeforeOkWait = null;
|
||||
this._freezeMotion = false;
|
||||
this._lastSegmentMs = 0;
|
||||
|
||||
// --- Web Serial ---
|
||||
// --- Serial: one command in flight, wait for its ok ---
|
||||
this.port = null;
|
||||
this.writer = null;
|
||||
this.reader = null;
|
||||
this.readLoopActive = false;
|
||||
this._rxBuf = '';
|
||||
this._okResolvers = [];
|
||||
this._okWaiter = null;
|
||||
this._sendChain = Promise.resolve();
|
||||
this._okIgnoreUntil = 0;
|
||||
this._machineBusy = false;
|
||||
this._lastBusyLogAt = 0;
|
||||
|
||||
// --- Keyboard / buttons ---
|
||||
@@ -139,7 +132,6 @@ class WallPlotter {
|
||||
this._testLabel = 'Test';
|
||||
this._stopLabel = 'Stop';
|
||||
this._lastTestBtnClick = 0;
|
||||
this._connecting = false;
|
||||
|
||||
// --- Callbacks ---
|
||||
const userOnLog = options.onLog ?? (() => {});
|
||||
@@ -151,8 +143,7 @@ class WallPlotter {
|
||||
|
||||
const userOnStatusChange = options.onStatusChange ?? (() => {});
|
||||
this.onStatusChange = (status) => {
|
||||
if (status === 'connected') this.statusText = 'Connected';
|
||||
else if (status === 'disconnected') this.statusText = 'Disconnected';
|
||||
this._syncStatus();
|
||||
this._updatePlotButtons();
|
||||
userOnStatusChange(status);
|
||||
};
|
||||
@@ -161,6 +152,45 @@ class WallPlotter {
|
||||
this._installUnloadHandler();
|
||||
}
|
||||
|
||||
get connected() {
|
||||
return this._link === 'connected';
|
||||
}
|
||||
|
||||
get isPlotting() {
|
||||
return this._job !== 'idle';
|
||||
}
|
||||
|
||||
get isPaused() {
|
||||
return this._job === 'paused';
|
||||
}
|
||||
|
||||
_setJob(next) {
|
||||
if (this._job === next) return;
|
||||
if (this._job === 'paused' && this._pauseStartedAt) {
|
||||
this._pausedAccumMs += performance.now() - this._pauseStartedAt;
|
||||
}
|
||||
this._job = next;
|
||||
this._pauseStartedAt = next === 'paused' ? performance.now() : 0;
|
||||
this._syncStatus();
|
||||
this._updatePlotButtons();
|
||||
this._redrawSketch();
|
||||
}
|
||||
|
||||
_abortJob() {
|
||||
if (this._job === 'idle') return;
|
||||
this._setJob('aborting');
|
||||
}
|
||||
|
||||
_syncStatus() {
|
||||
if (this._link === 'disconnected') this.statusText = 'Disconnected';
|
||||
else if (this._link === 'connecting') this.statusText = 'Connecting…';
|
||||
else if (this._job === 'paused') this.statusText = 'Paused';
|
||||
else if (this._job === 'aborting') this.statusText = 'Stopped';
|
||||
else if (this._job === 'running') {
|
||||
this.statusText = this._plotTestOnly ? 'Testing' : 'Plotting';
|
||||
} else this.statusText = 'Connected';
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Public API — setup / UI
|
||||
// ===========================================================================
|
||||
@@ -319,40 +349,23 @@ class WallPlotter {
|
||||
return this._estimatePathDurationMs(commands ?? this._path, this.x, this.y);
|
||||
}
|
||||
|
||||
/** Abort the current runPath (browser side only) and unblock any `ok` wait. */
|
||||
/** Abort the current runPath after the in-flight command's `ok`. */
|
||||
stopPath() {
|
||||
this._pathAbort = true;
|
||||
this.isPaused = false;
|
||||
this._freezeMotion = true;
|
||||
this._cancelPendingOk();
|
||||
this._abortJob();
|
||||
}
|
||||
|
||||
/** Pause the current runPath after the in-flight segment. */
|
||||
pausePlot() {
|
||||
if (!this.isPlotting || this.isPaused) return;
|
||||
this.isPaused = true;
|
||||
this._pauseStartedAt = performance.now();
|
||||
this._statusBeforePause = this.statusText;
|
||||
this.statusText = 'Paused';
|
||||
if (this._job !== 'running') return;
|
||||
this._setJob('paused');
|
||||
this.onLog('plot paused');
|
||||
this._updatePlotButtons();
|
||||
this._redrawSketch();
|
||||
}
|
||||
|
||||
/** Continue a paused runPath (including after Stop or a timeout). */
|
||||
resumePlot() {
|
||||
if (!this.isPlotting || !this.isPaused) return;
|
||||
if (this._pauseStartedAt) {
|
||||
this._pausedAccumMs += performance.now() - this._pauseStartedAt;
|
||||
}
|
||||
this._pauseStartedAt = 0;
|
||||
this.isPaused = false;
|
||||
this._freezeMotion = false;
|
||||
if (this._statusBeforePause) this.statusText = this._statusBeforePause;
|
||||
this._statusBeforePause = null;
|
||||
if (this._job !== 'paused') return;
|
||||
this._setJob('running');
|
||||
this.onLog('plot resumed');
|
||||
this._updatePlotButtons();
|
||||
this._redrawSketch();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -362,27 +375,24 @@ class WallPlotter {
|
||||
* a running move, and on a polargraph it desyncs belt lengths.
|
||||
*/
|
||||
async stopPlot() {
|
||||
const hadWait = this._okResolvers.length > 0;
|
||||
|
||||
if (this.isPlotting && this.isPaused) {
|
||||
this.stopPath();
|
||||
this.statusText = 'Stopped — current stroke may still finish';
|
||||
if (this._job === 'paused') {
|
||||
this._abortJob();
|
||||
this.onLog(
|
||||
'plot aborted — motors finish the in-flight move (unplug power for a hard stop)'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isPlotting) {
|
||||
if (this._job === 'running') {
|
||||
this.pausePlot();
|
||||
this.onLog('plot stopped after current move — resume to continue');
|
||||
return;
|
||||
}
|
||||
|
||||
if (hadWait) {
|
||||
if (this._okWaiter) {
|
||||
this._cancelPendingOk();
|
||||
this.onLog('serial wait cancelled');
|
||||
this.statusText = this.connected ? 'Connected' : 'Disconnected';
|
||||
this._syncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,7 +405,7 @@ class WallPlotter {
|
||||
if (!('serial' in navigator)) {
|
||||
throw new Error('Web Serial is not supported in this browser. Use Chrome or Edge.');
|
||||
}
|
||||
if (this.connected) return;
|
||||
if (this._link === 'connected') return;
|
||||
|
||||
let port;
|
||||
try {
|
||||
@@ -410,7 +420,7 @@ class WallPlotter {
|
||||
|
||||
this.writer = this.port.writable.getWriter();
|
||||
this._startReadLoop();
|
||||
this.connected = true;
|
||||
this._link = 'connected';
|
||||
this.homeSet = false;
|
||||
this._homeWarning = 'Home not set — jog to origin and press H';
|
||||
this.onStatusChange('connected');
|
||||
@@ -462,12 +472,11 @@ class WallPlotter {
|
||||
|
||||
/** Close the serial port. */
|
||||
async disconnect() {
|
||||
if (!this.connected && !this.port) return;
|
||||
if (this._link === 'disconnected' && !this.port) return;
|
||||
|
||||
this.readLoopActive = false;
|
||||
this._cancelPendingOk();
|
||||
this._rxBuf = '';
|
||||
this._machineBusy = false;
|
||||
|
||||
const reader = this.reader;
|
||||
const writer = this.writer;
|
||||
@@ -503,7 +512,7 @@ class WallPlotter {
|
||||
}
|
||||
}
|
||||
|
||||
this.connected = false;
|
||||
this._link = 'disconnected';
|
||||
this.homeSet = false;
|
||||
this._homeWarning = null;
|
||||
this.onStatusChange('disconnected');
|
||||
@@ -511,18 +520,17 @@ class WallPlotter {
|
||||
|
||||
/** Best-effort close for page reload (must not await — Mac keeps the USB lock otherwise). */
|
||||
_releaseSerialNow() {
|
||||
if (!this.port && !this.connected) return;
|
||||
if (!this.port && this._link === 'disconnected') return;
|
||||
this.readLoopActive = false;
|
||||
this._cancelPendingOk();
|
||||
this._rxBuf = '';
|
||||
this._machineBusy = false;
|
||||
const reader = this.reader;
|
||||
const writer = this.writer;
|
||||
const port = this.port;
|
||||
this.reader = null;
|
||||
this.writer = null;
|
||||
this.port = null;
|
||||
this.connected = false;
|
||||
this._link = 'disconnected';
|
||||
this.homeSet = false;
|
||||
try {
|
||||
reader?.cancel();
|
||||
@@ -594,25 +602,26 @@ class WallPlotter {
|
||||
})
|
||||
: null;
|
||||
try {
|
||||
const written = this.writer.write(
|
||||
new TextEncoder().encode(command + '\n')
|
||||
);
|
||||
const written = this.writer
|
||||
.write(new TextEncoder().encode(command + '\n'))
|
||||
.then(() => this.onLog('→ ' + command));
|
||||
if (okPromise) {
|
||||
await Promise.all([
|
||||
written.then(() => this.onLog('→ ' + command)),
|
||||
written.catch((err) => {
|
||||
this._cancelPendingOk();
|
||||
throw err;
|
||||
}),
|
||||
okPromise,
|
||||
]);
|
||||
} else {
|
||||
await written;
|
||||
this.onLog('→ ' + command);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') throw err;
|
||||
this._okIgnoreUntil = performance.now() + 400;
|
||||
const timeoutErr = new Error(err.message);
|
||||
timeoutErr.name = 'TimeoutError';
|
||||
this.onLog('! ' + err.message);
|
||||
if (!this.isPlotting) this.statusText = 'Error: ' + err.message;
|
||||
if (this._job === 'idle') this.statusText = 'Error: ' + err.message;
|
||||
throw timeoutErr;
|
||||
}
|
||||
}
|
||||
@@ -680,12 +689,12 @@ class WallPlotter {
|
||||
|
||||
/** G92 X0 Y0 — set current pose as home. Refused while plotting or waiting for Marlin. */
|
||||
async setHomeHere() {
|
||||
if (this.isPlotting) {
|
||||
if (this._job !== 'idle') {
|
||||
this.onLog('! cannot set home while plotting — resume or abort first');
|
||||
this.statusText = 'Finish or abort plot before H';
|
||||
return;
|
||||
}
|
||||
if (!this._isMachineIdle()) {
|
||||
if (this._okWaiter) {
|
||||
this.onLog('! plotter busy — wait for ok before setting home');
|
||||
this.statusText = 'Waiting for plotter…';
|
||||
return;
|
||||
@@ -700,7 +709,7 @@ class WallPlotter {
|
||||
this.y = 0;
|
||||
this.homeSet = true;
|
||||
this._homeWarning = null;
|
||||
if (this.connected) this.statusText = 'Connected';
|
||||
this._syncStatus();
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
@@ -721,39 +730,38 @@ class WallPlotter {
|
||||
// ===========================================================================
|
||||
|
||||
async _executePath(commands, testOnly = false) {
|
||||
if (!testOnly && !this._requireHome()) return;
|
||||
|
||||
const runId = ++this._pathRunId;
|
||||
this._pathAbort = false;
|
||||
if (this._pathLoopActive) this._abortJob();
|
||||
while (this._pathLoopActive) {
|
||||
if (runId !== this._pathRunId) return;
|
||||
await this._delay(16);
|
||||
}
|
||||
if (runId !== this._pathRunId) return;
|
||||
|
||||
this._pathLoopActive = true;
|
||||
this._plotTestOnly = testOnly;
|
||||
this.previewPath = commands;
|
||||
|
||||
if (!testOnly && !this._requireHome()) {
|
||||
this._plotTestOnly = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.isPlotting = true;
|
||||
this.isPaused = false;
|
||||
this.plotProgress = 0;
|
||||
this._plotStartedAt = performance.now();
|
||||
this._plotEstimateMs = this._estimatePathDurationMs(commands, this.x, this.y);
|
||||
this._pausedAccumMs = 0;
|
||||
this._pauseStartedAt = 0;
|
||||
this._statusBeforePause = null;
|
||||
this._freezeMotion = false;
|
||||
this._lastSegmentMs = 0;
|
||||
this._updatePlotButtons();
|
||||
this._setJob('running');
|
||||
|
||||
const savedFeedTravel = this.feedRateTravel;
|
||||
const savedFeedDraw = this.feedRateDraw;
|
||||
let px = this.x;
|
||||
let py = this.y;
|
||||
|
||||
const stopped = () => this._job === 'aborting' || runId !== this._pathRunId;
|
||||
|
||||
try {
|
||||
for (let i = 0; i < commands.length; i++) {
|
||||
if (this._pathAbort || runId !== this._pathRunId) break;
|
||||
if (stopped()) break;
|
||||
await this._waitIfPaused();
|
||||
this._freezeMotion = false;
|
||||
if (this._pathAbort || runId !== this._pathRunId) break;
|
||||
if (stopped()) break;
|
||||
const cmd = commands[i];
|
||||
|
||||
if (cmd.type === 'speed') {
|
||||
@@ -770,6 +778,7 @@ class WallPlotter {
|
||||
if (!testOnly && ms > 0) {
|
||||
await this.send(`G4 P${Math.round(ms)}`, {
|
||||
timeoutMs: Math.max(WallPlotter.OK_TIMEOUT_MS, ms + 5000),
|
||||
waitLogAfterMs: Math.max(WallPlotter.OK_WAIT_LOG_MS, ms + 1500),
|
||||
});
|
||||
}
|
||||
await this._holdPosition(ms);
|
||||
@@ -787,34 +796,21 @@ class WallPlotter {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') break;
|
||||
if (err.name === 'AbortError' || stopped()) break;
|
||||
if (err.name === 'TimeoutError') {
|
||||
if (!this.isPaused) this.pausePlot();
|
||||
if (this._job === 'running') this.pausePlot();
|
||||
this.onLog('segment skipped after timeout — resume continues from here');
|
||||
px = this.x;
|
||||
py = this.y;
|
||||
await this._waitIfPaused();
|
||||
if (this._pathAbort || runId !== this._pathRunId) break;
|
||||
if (cmd.type === 'move' || cmd.type === 'draw') {
|
||||
px = this.x;
|
||||
py = this.y;
|
||||
}
|
||||
if (stopped()) break;
|
||||
this.plotProgress = i + 1;
|
||||
continue;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (this._pathAbort || runId !== this._pathRunId) break;
|
||||
|
||||
if (this._freezeMotion) {
|
||||
px = this.x;
|
||||
py = this.y;
|
||||
await this._waitIfPaused();
|
||||
this._freezeMotion = false;
|
||||
if (this._pathAbort || runId !== this._pathRunId) break;
|
||||
continue;
|
||||
}
|
||||
if (stopped()) break;
|
||||
|
||||
if (cmd.type === 'move' || cmd.type === 'draw') {
|
||||
px = cmd.x;
|
||||
@@ -838,18 +834,9 @@ class WallPlotter {
|
||||
} else {
|
||||
this.isPenDown = false;
|
||||
}
|
||||
this.isPlotting = false;
|
||||
this.isPaused = false;
|
||||
this._pauseStartedAt = 0;
|
||||
this._statusBeforePause = null;
|
||||
this._plotTestOnly = false;
|
||||
this._updatePlotButtons();
|
||||
this._redrawSketch();
|
||||
|
||||
const next = this._pendingAfterAbort;
|
||||
this._pendingAfterAbort = null;
|
||||
if (next === 'test') this._triggerSketchFn('test');
|
||||
else if (next === 'plot') this._triggerSketchFn('plot');
|
||||
this._setJob('idle');
|
||||
this._pathLoopActive = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -903,11 +890,11 @@ class WallPlotter {
|
||||
let elapsed = 0;
|
||||
let last = performance.now();
|
||||
while (elapsed < ms) {
|
||||
if (this._pathAbort || this._freezeMotion) return;
|
||||
if (this._plotTestOnly && this.isPaused) {
|
||||
if (this._job === 'aborting') return;
|
||||
if (this._job === 'paused') {
|
||||
await this._waitIfPaused();
|
||||
last = performance.now();
|
||||
if (this._pathAbort) return;
|
||||
if (this._job === 'aborting') return;
|
||||
}
|
||||
const now = performance.now();
|
||||
elapsed += now - last;
|
||||
@@ -924,11 +911,11 @@ class WallPlotter {
|
||||
let last = performance.now();
|
||||
|
||||
while (true) {
|
||||
if (this._pathAbort || this._freezeMotion) break;
|
||||
if (this._plotTestOnly && this.isPaused) {
|
||||
if (this._job === 'aborting') break;
|
||||
if (this._plotTestOnly && this._job === 'paused') {
|
||||
await this._waitIfPaused();
|
||||
last = performance.now();
|
||||
if (this._pathAbort || this._freezeMotion) break;
|
||||
if (this._job === 'aborting') break;
|
||||
}
|
||||
const now = performance.now();
|
||||
elapsed += now - last;
|
||||
@@ -940,7 +927,7 @@ class WallPlotter {
|
||||
if (t >= 1) break;
|
||||
await this._delay(this.previewFrameMs);
|
||||
}
|
||||
if (!this._pathAbort && !this._freezeMotion) {
|
||||
if (this._job !== 'aborting') {
|
||||
this.x = cmd.x;
|
||||
this.y = cmd.y;
|
||||
}
|
||||
@@ -948,7 +935,7 @@ class WallPlotter {
|
||||
|
||||
/** Wait while paused. Live plots lift the pen until the next segment. */
|
||||
async _waitIfPaused() {
|
||||
if (!this.isPaused) return;
|
||||
if (this._job !== 'paused') return;
|
||||
|
||||
if (!this._plotTestOnly && this.connected && this.isPenDown) {
|
||||
try {
|
||||
@@ -958,7 +945,7 @@ class WallPlotter {
|
||||
}
|
||||
}
|
||||
|
||||
while (this.isPaused && !this._pathAbort) {
|
||||
while (this._job === 'paused') {
|
||||
this._redrawSketch();
|
||||
await this._delay(this.previewFrameMs);
|
||||
}
|
||||
@@ -1014,6 +1001,11 @@ class WallPlotter {
|
||||
const waitLabel = command ? ` (${command})` : '';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this._okWaiter) {
|
||||
reject(new Error('serial already waiting for ok'));
|
||||
return;
|
||||
}
|
||||
|
||||
let settled = false;
|
||||
let timer = null;
|
||||
let waitLogTimer = null;
|
||||
@@ -1022,11 +1014,10 @@ class WallPlotter {
|
||||
const finish = (fn, arg) => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
if (this._okWaiter === entry) this._okWaiter = null;
|
||||
clearTimeout(timer);
|
||||
clearTimeout(waitLogTimer);
|
||||
if (waitLogInterval != null) clearInterval(waitLogInterval);
|
||||
const idx = this._okResolvers.indexOf(entry);
|
||||
if (idx >= 0) this._okResolvers.splice(idx, 1);
|
||||
fn(arg);
|
||||
};
|
||||
|
||||
@@ -1043,13 +1034,8 @@ class WallPlotter {
|
||||
|
||||
const entry = {
|
||||
resolve: () => {
|
||||
if (this.statusText === 'Waiting for plotter…') {
|
||||
this.statusText =
|
||||
this._statusBeforeOkWait ??
|
||||
(this.connected ? 'Connected' : 'Disconnected');
|
||||
this._statusBeforeOkWait = null;
|
||||
}
|
||||
finish(resolve);
|
||||
this._syncStatus();
|
||||
},
|
||||
reject: (err) => finish(reject, err),
|
||||
bump: () => {
|
||||
@@ -1057,15 +1043,13 @@ class WallPlotter {
|
||||
},
|
||||
};
|
||||
|
||||
this._okWaiter = entry;
|
||||
armTimer();
|
||||
|
||||
waitLogTimer = setTimeout(() => {
|
||||
if (settled) return;
|
||||
const started = performance.now();
|
||||
this.onLog(`… waiting for Marlin ok${waitLabel}`);
|
||||
if (this.statusText !== 'Waiting for plotter…') {
|
||||
this._statusBeforeOkWait = this.statusText;
|
||||
}
|
||||
this.statusText = 'Waiting for plotter…';
|
||||
this._redrawSketch();
|
||||
waitLogInterval = setInterval(() => {
|
||||
@@ -1073,19 +1057,15 @@ class WallPlotter {
|
||||
this.onLog(`… still waiting for Marlin ok${waitLabel} (${s}s)`);
|
||||
}, WallPlotter.OK_WAIT_PING_MS);
|
||||
}, waitLogAfterMs);
|
||||
|
||||
this._okResolvers.push(entry);
|
||||
});
|
||||
}
|
||||
|
||||
_cancelPendingOk() {
|
||||
this._okIgnoreUntil = performance.now() + 400;
|
||||
const pending = this._okResolvers.slice();
|
||||
for (const entry of pending) {
|
||||
const waiter = this._okWaiter;
|
||||
if (!waiter) return;
|
||||
const err = new Error('aborted');
|
||||
err.name = 'AbortError';
|
||||
entry.reject(err);
|
||||
}
|
||||
waiter.reject(err);
|
||||
}
|
||||
|
||||
_startReadLoop() {
|
||||
@@ -1137,9 +1117,8 @@ class WallPlotter {
|
||||
|
||||
_handleResponse(line) {
|
||||
if (/busy:\s*processing/i.test(line)) {
|
||||
this._machineBusy = true;
|
||||
for (const entry of this._okResolvers) {
|
||||
if (typeof entry.bump === 'function') entry.bump();
|
||||
if (this._okWaiter && typeof this._okWaiter.bump === 'function') {
|
||||
this._okWaiter.bump();
|
||||
}
|
||||
const now = performance.now();
|
||||
if (now - this._lastBusyLogAt > 4000) {
|
||||
@@ -1150,13 +1129,8 @@ class WallPlotter {
|
||||
}
|
||||
this.onLog('← ' + line);
|
||||
if (/^(echo:\s*)?ok\b/i.test(line)) {
|
||||
this._machineBusy = false;
|
||||
const next = this._okResolvers.shift();
|
||||
if (next) {
|
||||
next.resolve();
|
||||
} else if (performance.now() < this._okIgnoreUntil) {
|
||||
this.onLog('← ok ignored (stale, no waiter)');
|
||||
}
|
||||
const waiter = this._okWaiter;
|
||||
if (waiter) waiter.resolve();
|
||||
}
|
||||
this._parseMachineBounds(line);
|
||||
if (line.startsWith('X:')) {
|
||||
@@ -1190,7 +1164,7 @@ class WallPlotter {
|
||||
}
|
||||
|
||||
_isMachineIdle() {
|
||||
return this._okResolvers.length === 0 && !this._machineBusy;
|
||||
return this._job === 'idle' && !this._okWaiter;
|
||||
}
|
||||
|
||||
_showHomeRequiredWarning() {
|
||||
@@ -1236,7 +1210,7 @@ class WallPlotter {
|
||||
return false;
|
||||
}
|
||||
if (k === '0') {
|
||||
if (this.isPlotting || !this._isMachineIdle()) return false;
|
||||
if (!this._isMachineIdle()) return false;
|
||||
await this.goToZero();
|
||||
return false;
|
||||
}
|
||||
@@ -1256,8 +1230,8 @@ class WallPlotter {
|
||||
}
|
||||
|
||||
_handleContinuousJog() {
|
||||
if (!this.connected || this._jogInFlight || this.isPlotting) return;
|
||||
if (!this.homeSet || !this._isMachineIdle()) return;
|
||||
if (!this.connected || this._jogInFlight || !this._isMachineIdle()) return;
|
||||
if (!this.homeSet) return;
|
||||
|
||||
const draw = keyIsDown(SHIFT);
|
||||
const s = this.stepSize;
|
||||
@@ -1307,81 +1281,80 @@ class WallPlotter {
|
||||
}
|
||||
|
||||
async _toggleConnection() {
|
||||
if (this._connecting) return;
|
||||
if (this._link === 'connecting') return;
|
||||
try {
|
||||
if (this.connected) await this.disconnect();
|
||||
else {
|
||||
this._connecting = true;
|
||||
this.statusText = 'Connecting…';
|
||||
this._link = 'connecting';
|
||||
this._syncStatus();
|
||||
this._updatePlotButtons();
|
||||
await this.connect();
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') return;
|
||||
this._link = 'disconnected';
|
||||
this.statusText = 'Error: ' + err.message;
|
||||
this.onLog('! ' + err.message);
|
||||
} finally {
|
||||
this._connecting = false;
|
||||
if (this._link === 'connecting') this._link = 'disconnected';
|
||||
this._updatePlotButtons();
|
||||
}
|
||||
}
|
||||
|
||||
_onConnectButton() {
|
||||
if (this.connected || this._connecting) return;
|
||||
if (this._link !== 'disconnected') return;
|
||||
this._toggleConnection();
|
||||
}
|
||||
|
||||
_onPlotButton() {
|
||||
if (this.isPlotting) {
|
||||
if (this.isPaused) this.resumePlot();
|
||||
else this.pausePlot();
|
||||
if (this._job === 'paused') {
|
||||
this.resumePlot();
|
||||
return;
|
||||
}
|
||||
if (!this.connected) return;
|
||||
if (this._job === 'running') {
|
||||
this.pausePlot();
|
||||
return;
|
||||
}
|
||||
if (this._job !== 'idle' || !this.connected) return;
|
||||
this._triggerSketchFn('plot');
|
||||
}
|
||||
|
||||
_onTestButton() {
|
||||
const now = millis();
|
||||
if (this.isPlotting && this._plotTestOnly) {
|
||||
if (now - this._lastTestBtnClick < 450) this._restartPathAfterAbort('test');
|
||||
if (this._job === 'running' && this._plotTestOnly) {
|
||||
if (now - this._lastTestBtnClick < 450) this._restartPath('test');
|
||||
else this.stopPlot();
|
||||
this._lastTestBtnClick = now;
|
||||
return;
|
||||
}
|
||||
this._lastTestBtnClick = now;
|
||||
if (this.isPlotting) {
|
||||
this._restartPathAfterAbort('test');
|
||||
return;
|
||||
}
|
||||
this._triggerSketchFn('test');
|
||||
this._restartPath('test');
|
||||
}
|
||||
|
||||
_restartPathAfterAbort(mode) {
|
||||
this._pendingAfterAbort = mode;
|
||||
this._pathAbort = true;
|
||||
this.isPaused = false;
|
||||
this._freezeMotion = true;
|
||||
this._cancelPendingOk();
|
||||
_restartPath(mode) {
|
||||
if (this._pathLoopActive) this._abortJob();
|
||||
this._triggerSketchFn(mode);
|
||||
}
|
||||
|
||||
_updatePlotButtons() {
|
||||
if (this._connectBtn) {
|
||||
if (this.connected) this._connectBtn.hide();
|
||||
else this._connectBtn.show();
|
||||
this._connectBtn.elt.disabled = this._connecting;
|
||||
this._connectBtn.elt.disabled = this._link === 'connecting';
|
||||
}
|
||||
if (this._plotBtn) {
|
||||
let label = this._plotLabel;
|
||||
if (this.isPlotting) {
|
||||
label = this.isPaused ? this._resumeLabel : this._pauseLabel;
|
||||
if (this._job === 'paused') label = this._resumeLabel;
|
||||
else if (this._job === 'running' || this._job === 'aborting') {
|
||||
label = this._pauseLabel;
|
||||
}
|
||||
this._plotBtn.html(label);
|
||||
this._plotBtn.elt.disabled = !this.connected && !this.isPlotting;
|
||||
this._plotBtn.elt.disabled =
|
||||
this._job === 'aborting' || (!this.connected && this._job === 'idle');
|
||||
}
|
||||
if (this._testBtn) {
|
||||
this._testBtn.html(
|
||||
this.isPlotting && this._plotTestOnly ? this._stopLabel : this._testLabel
|
||||
this._job !== 'idle' && this._plotTestOnly ? this._stopLabel : this._testLabel
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1631,7 +1604,7 @@ class WallPlotter {
|
||||
if (this.isPlotting) {
|
||||
fill(180, 220, 255);
|
||||
text(
|
||||
`${this.isPaused ? 'paused' : 'plotting'} ${this.plotProgress}/${this.previewPath.length}`,
|
||||
`${this._job === 'paused' ? 'paused' : this._job === 'aborting' ? 'stopped' : 'plotting'} ${this.plotProgress}/${this.previewPath.length}`,
|
||||
16,
|
||||
152
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user