This commit is contained in:
Martin Fuchs
2026-08-16 16:08:28 +02:00
parent 606752fbec
commit 7b93d9a5d5
2 changed files with 92 additions and 66 deletions
+90 -64
View File
@@ -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++) {