jog fix
This commit is contained in:
+90
-64
@@ -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();
|
||||
this.statusText = 'Stopped';
|
||||
this.onLog('plot aborted');
|
||||
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 — 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 (!this._requireHome()) return;
|
||||
if (draw) {
|
||||
if (!this._requireHome()) return;
|
||||
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