jog fix
This commit is contained in:
+2
-2
@@ -152,11 +152,11 @@ Returns estimated duration in **milliseconds** for the current path buffer, or f
|
||||
|
||||
`stopPath()` aborts the current `runPath` and unblocks any wait for Marlin `ok`.
|
||||
|
||||
`stopPlot()` **halts and pauses** so **Resume** can continue from the current position (live plot: `M410` quick-stop, then `M114`). Key **S** and a Marlin `ok` timeout do the same. **S** / Stop again while paused aborts the run.
|
||||
`stopPlot()` first press is the same as **Pause**: finish the current stroke, then **Resume** continues. Second press is an emergency `M410` quick-stop. That leaves belt length unknown on a polargraph, so **home is cleared** — jog to origin and press **H** before plotting again. An `ok` timeout pauses the same way (no `M410`); that segment is skipped.
|
||||
|
||||
### `pausePlot()` / `resumePlot()`
|
||||
|
||||
**Pause** waits for the current segment to finish, then lifts the pen. **Stop** / timeout interrupt the current move immediately. Both leave the Plot button as **Resume**. Elapsed / remaining HUD time freezes while paused.
|
||||
**Pause** / first **Stop** wait for the current segment to finish (`M400`), then lift the pen. Do not resume after emergency stop until you re-home. Elapsed / remaining HUD time freezes while paused.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+89
-63
@@ -18,7 +18,7 @@ class WallPlotter {
|
||||
// Static constants
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static VERSION = '2.2.0';
|
||||
static VERSION = '2.2.2';
|
||||
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 */
|
||||
@@ -117,6 +117,10 @@ class WallPlotter {
|
||||
this.readLoopActive = false;
|
||||
this._rxBuf = '';
|
||||
this._okResolvers = [];
|
||||
this._sendChain = Promise.resolve();
|
||||
this._okIgnoreUntil = 0;
|
||||
this._machineBusy = false;
|
||||
this._lastBusyLogAt = 0;
|
||||
|
||||
// --- Keyboard / buttons ---
|
||||
this._keysEnabled = false;
|
||||
@@ -349,21 +353,34 @@ class WallPlotter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Halt the current move and pause so Resume can continue.
|
||||
* If already paused, abort the run. Also cancels a stuck `ok` wait.
|
||||
* First press: pause after the current stroke (safe to Resume).
|
||||
* Second press: emergency M410 — home is cleared, do not resume.
|
||||
*/
|
||||
async stopPlot() {
|
||||
const hadWait = this._okResolvers.length > 0;
|
||||
|
||||
if (this.isPlotting && this.isPaused) {
|
||||
this.stopPath();
|
||||
if (this.connected && !this._plotTestOnly) {
|
||||
try {
|
||||
await this.send('M410', { waitOk: false, allowWithoutHome: true });
|
||||
} catch (_) {
|
||||
/* port may be busy */
|
||||
}
|
||||
this.homeSet = false;
|
||||
this._okIgnoreUntil = performance.now() + 1500;
|
||||
this._homeWarning = 'Re-home (H) after emergency stop — position is unknown';
|
||||
this.statusText = this._homeWarning;
|
||||
} else {
|
||||
this.statusText = 'Stopped';
|
||||
this.onLog('plot aborted');
|
||||
}
|
||||
this.onLog('plot aborted — set home before plotting again');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isPlotting) {
|
||||
await this._haltToPause('stopped');
|
||||
this.pausePlot();
|
||||
this.onLog('plot stopped after current move — resume to continue');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -451,6 +468,12 @@ class WallPlotter {
|
||||
* @param {{allowWithoutHome?: boolean, waitOk?: boolean, timeoutMs?: number}} [options]
|
||||
*/
|
||||
async send(line, options = {}) {
|
||||
const queued = this._sendChain.then(() => this._sendNow(line, options));
|
||||
this._sendChain = queued.catch(() => {});
|
||||
return queued;
|
||||
}
|
||||
|
||||
async _sendNow(line, options = {}) {
|
||||
if (!this.connected || !this.writer) {
|
||||
throw new Error('Not connected to plotter');
|
||||
}
|
||||
@@ -478,6 +501,7 @@ class WallPlotter {
|
||||
await okPromise;
|
||||
} 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);
|
||||
@@ -547,9 +571,24 @@ class WallPlotter {
|
||||
else await this.moveTo(this.x + dx, this.y + dy);
|
||||
}
|
||||
|
||||
/** G92 X0 Y0 — set current pose as home. */
|
||||
/** G92 X0 Y0 — set current pose as home. Refused while plotting or waiting for Marlin. */
|
||||
async setHomeHere() {
|
||||
if (this.isPlotting) {
|
||||
this.onLog('! cannot set home while plotting — resume or abort first');
|
||||
this.statusText = 'Finish or abort plot before H';
|
||||
return;
|
||||
}
|
||||
if (!this._isMachineIdle()) {
|
||||
this.onLog('! plotter busy — wait for ok before setting home');
|
||||
this.statusText = 'Waiting for plotter…';
|
||||
return;
|
||||
}
|
||||
await this.send('G92 X0 Y0');
|
||||
try {
|
||||
await this.send('M114', { timeoutMs: 3000, allowWithoutHome: true });
|
||||
} catch (_) {
|
||||
/* position report optional */
|
||||
}
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
this.homeSet = true;
|
||||
@@ -618,7 +657,6 @@ class WallPlotter {
|
||||
continue;
|
||||
}
|
||||
|
||||
let retry = false;
|
||||
try {
|
||||
if (cmd.type === 'wait') {
|
||||
const ms = Math.max(0, cmd.ms ?? 0);
|
||||
@@ -642,27 +680,32 @@ class WallPlotter {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (err.name === 'AbortError') {
|
||||
retry = this.isPaused && !this._pathAbort;
|
||||
if (!retry) break;
|
||||
} else if (err.name === 'TimeoutError') {
|
||||
if (!this.isPaused) await this._haltToPause('timeout');
|
||||
retry = !this._pathAbort;
|
||||
if (!retry) break;
|
||||
} else {
|
||||
throw err;
|
||||
if (err.name === 'AbortError') break;
|
||||
if (err.name === 'TimeoutError') {
|
||||
if (!this.isPaused) 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;
|
||||
}
|
||||
this.plotProgress = i + 1;
|
||||
continue;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (this._pathAbort || runId !== this._pathRunId) break;
|
||||
|
||||
if (retry || this._freezeMotion) {
|
||||
if (this._freezeMotion) {
|
||||
px = this.x;
|
||||
py = this.y;
|
||||
await this._waitIfPaused();
|
||||
this._freezeMotion = false;
|
||||
if (this._pathAbort || runId !== this._pathRunId) break;
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -787,45 +830,6 @@ class WallPlotter {
|
||||
}
|
||||
}
|
||||
|
||||
/** Interrupt the current move (M410) and pause so Resume can continue. */
|
||||
async _haltToPause(reason) {
|
||||
if (!this.isPlotting || this.isPaused) return;
|
||||
this._freezeMotion = true;
|
||||
this._cancelPendingOk();
|
||||
this.isPaused = true;
|
||||
this._pauseStartedAt = performance.now();
|
||||
this._statusBeforePause = this.connected ? 'Connected' : 'Disconnected';
|
||||
this.statusText = 'Paused';
|
||||
this.onLog(
|
||||
reason === 'timeout'
|
||||
? 'plot paused after timeout — resume to continue'
|
||||
: 'plot stopped — resume to continue'
|
||||
);
|
||||
this._updatePlotButtons();
|
||||
this._redrawSketch();
|
||||
|
||||
if (this.connected && !this._plotTestOnly) {
|
||||
try {
|
||||
await this.send('M410', { waitOk: false, allowWithoutHome: true });
|
||||
} catch (_) {
|
||||
/* port may be busy */
|
||||
}
|
||||
try {
|
||||
await this.send('M114', {
|
||||
timeoutMs: 3000,
|
||||
allowWithoutHome: true,
|
||||
});
|
||||
} catch (_) {
|
||||
/* USB may be dead */
|
||||
}
|
||||
try {
|
||||
await this.penUp();
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Wait while paused. Live plots lift the pen until the next segment. */
|
||||
async _waitIfPaused() {
|
||||
if (!this.isPaused) return;
|
||||
@@ -927,6 +931,7 @@ class WallPlotter {
|
||||
|
||||
const waitLogTimer = setTimeout(() => {
|
||||
if (settled) return;
|
||||
const started = performance.now();
|
||||
this.onLog('… waiting for Marlin ok');
|
||||
if (this.statusText !== 'Waiting for plotter…') {
|
||||
this._statusBeforeOkWait = this.statusText;
|
||||
@@ -934,7 +939,8 @@ class WallPlotter {
|
||||
this.statusText = 'Waiting for plotter…';
|
||||
this._redrawSketch();
|
||||
waitLogInterval = setInterval(() => {
|
||||
this.onLog('… still waiting for Marlin ok');
|
||||
const s = Math.round((performance.now() - started) / 1000);
|
||||
this.onLog(`… still waiting for Marlin ok (${s}s)`);
|
||||
}, WallPlotter.OK_WAIT_PING_MS);
|
||||
}, WallPlotter.OK_WAIT_LOG_MS);
|
||||
|
||||
@@ -943,6 +949,7 @@ class WallPlotter {
|
||||
}
|
||||
|
||||
_cancelPendingOk() {
|
||||
this._okIgnoreUntil = performance.now() + 400;
|
||||
const pending = this._okResolvers.slice();
|
||||
for (const entry of pending) {
|
||||
const err = new Error('aborted');
|
||||
@@ -988,8 +995,22 @@ class WallPlotter {
|
||||
}
|
||||
|
||||
_handleResponse(line) {
|
||||
if (/busy:\s*processing/i.test(line)) {
|
||||
this._machineBusy = true;
|
||||
const now = performance.now();
|
||||
if (now - this._lastBusyLogAt > 4000) {
|
||||
this._lastBusyLogAt = now;
|
||||
this.onLog('← busy (move still running)');
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.onLog('← ' + line);
|
||||
if (/^ok\b/i.test(line)) {
|
||||
this._machineBusy = false;
|
||||
if (performance.now() < this._okIgnoreUntil) {
|
||||
this.onLog('← ok ignored (stale)');
|
||||
return;
|
||||
}
|
||||
const next = this._okResolvers.shift();
|
||||
if (next) next.resolve();
|
||||
}
|
||||
@@ -1024,6 +1045,10 @@ class WallPlotter {
|
||||
return /^G[01]\b/i.test(command) && /\b[XY]/i.test(command);
|
||||
}
|
||||
|
||||
_isMachineIdle() {
|
||||
return this._okResolvers.length === 0 && !this._machineBusy;
|
||||
}
|
||||
|
||||
_showHomeRequiredWarning() {
|
||||
const msg = 'Home not set — press H before sending moves';
|
||||
this._homeWarning = msg;
|
||||
@@ -1063,6 +1088,7 @@ class WallPlotter {
|
||||
return false;
|
||||
}
|
||||
if (k === '0') {
|
||||
if (this.isPlotting || !this._isMachineIdle()) return false;
|
||||
await this.goToZero();
|
||||
return false;
|
||||
}
|
||||
@@ -1082,7 +1108,8 @@ class WallPlotter {
|
||||
}
|
||||
|
||||
_handleContinuousJog() {
|
||||
if (!this.connected || this._jogInFlight) return;
|
||||
if (!this.connected || this._jogInFlight || this.isPlotting) return;
|
||||
if (!this.homeSet || !this._isMachineIdle()) return;
|
||||
|
||||
const draw = keyIsDown(SHIFT);
|
||||
const s = this.stepSize;
|
||||
@@ -1112,8 +1139,8 @@ class WallPlotter {
|
||||
|
||||
async _jogBy(dx, dy, draw = false) {
|
||||
try {
|
||||
if (draw) {
|
||||
if (!this._requireHome()) return;
|
||||
if (draw) {
|
||||
await this.drawTo(this.x + dx, this.y + dy);
|
||||
return;
|
||||
}
|
||||
@@ -1121,8 +1148,7 @@ class WallPlotter {
|
||||
const nx = this.x + dx;
|
||||
const ny = this.y + dy;
|
||||
await this.send(
|
||||
`G1 X${this._fmt(nx)} Y${this._fmt(ny)} F${this.feedRateTravel}`,
|
||||
{ allowWithoutHome: true }
|
||||
`G1 X${this._fmt(nx)} Y${this._fmt(ny)} F${this.feedRateTravel}`
|
||||
);
|
||||
this.x = nx;
|
||||
this.y = ny;
|
||||
@@ -1491,7 +1517,7 @@ class WallPlotter {
|
||||
'H set current pos as home (G92) — required first',
|
||||
'0 go to origin (0, 0)',
|
||||
'P toggle pen (test up/down angles)',
|
||||
'S halt plot (Resume to continue, S again to abort)',
|
||||
'S stop after current stroke (S again = e-stop, re-home)',
|
||||
'C connect / disconnect',
|
||||
];
|
||||
for (let i = 0; i < help.length; i++) {
|
||||
|
||||
Reference in New Issue
Block a user