From a87b373b9e8f86842c6124d13756c8af376f3c49 Mon Sep 17 00:00:00 2001 From: Martin Fuchs Date: Sun, 16 Aug 2026 19:56:38 +0200 Subject: [PATCH] ok fix? --- examples/netz/index.html | 52 +++++++++++++++ examples/netz/sketch.js | 109 ++++++++++++++++++++++++++++++ examples/zickzack/index.html | 2 +- lib/README.md | 1 + lib/WallPlotter.js | 125 +++++++++++++++++++++++++---------- 5 files changed, 252 insertions(+), 37 deletions(-) create mode 100644 examples/netz/index.html create mode 100644 examples/netz/sketch.js diff --git a/examples/netz/index.html b/examples/netz/index.html new file mode 100644 index 0000000..43a2556 --- /dev/null +++ b/examples/netz/index.html @@ -0,0 +1,52 @@ + + + + + + Wall Plotter — zigzag + + + + + + +

Wall Plotter — zigzag

+
+ + diff --git a/examples/netz/sketch.js b/examples/netz/sketch.js new file mode 100644 index 0000000..2c98882 --- /dev/null +++ b/examples/netz/sketch.js @@ -0,0 +1,109 @@ +let wp; + +let area = 320; + +function setup() { + const canvas = createCanvas(windowWidth, windowHeight); + canvas.parent('sketch-host'); + + wp = new WallPlotter({ + penUpGcode: 'M280 P0 S120', + penDownGcode: 'M280 P0 S30', + penUpDelayMs: 250, + penDownDelayMs: 150, + }); + wp.setSpeed(10000, 10000); + wp.setTimingMultiplier(1); // tune if machine is slower than speed estimate, the higher the slower + wp.enableKeyInputs(); + wp.enablePlotButtons(); +} + +function draw() { + background(28, 28, 32); + wp.showInfos(); +} + +function windowResized() { + resizeCanvas(windowWidth, windowHeight); +} + +async function plot() { + await drawArt(false); +} + +async function test() { + await drawArt(true); +} + +async function drawArt(testOnly) { + wp.startPath(); + + + wp.move(-area, -area); + //wp.wait(1000 * 10); + + let step = area / 40; + + let points = []; + let last_points = []; + let row = -1; + + for (let y = -area; y <= area; y+=step){ + + // clear points + if(points.length>0){ + last_points = deepCopy(points); + points = []; + } + + let px = -area; + let py = y+random(10) + if (row==1){ + py = last_points[0].y; + } + + + wp.move(px, py); + points.push(createVector(px,py)); + + let index = 1; + for (let x = -area+step; x <= area; x+=step){ + px = x; + py = y+random(step); + + if (row==1 && index%2==0){ + py = last_points[index].y + } + + if (row==1 && index%2!=0){ + py = last_points[index-1].y+random(step*3); + } + + if (last_points.length>0 && row==-1 && index%2!=0){ + py = last_points[index].y + } + + if (last_points.length>0 && row==-1 && index%2==0){ + py = last_points[index-1].y+random(step*1); + } + + wp.draw(px, py); + points.push(createVector(px,py)); + + index++; + } + + row = row*-1; + + } + await wp.runPath(testOnly); +} + + +function deepCopy(copy_array){ + let new_array = [] + for(let i=0; i - + diff --git a/lib/README.md b/lib/README.md index 4aa1779..2665817 100644 --- a/lib/README.md +++ b/lib/README.md @@ -47,6 +47,7 @@ Keyboard controls. Call from `setup()`. No arguments. | Key | Action | |-----|--------| | `C` | Connect / disconnect | +| `T` | Run Test (preview) | | `H` | Set home here (`G92 X0 Y0`) — required before Plot | | `0` | Travel to `(0, 0)` | | `P` | Toggle pen | diff --git a/lib/WallPlotter.js b/lib/WallPlotter.js index 42b9aee..89dda8e 100644 --- a/lib/WallPlotter.js +++ b/lib/WallPlotter.js @@ -18,7 +18,7 @@ class WallPlotter { // Static constants // --------------------------------------------------------------------------- - static VERSION = '2.2.3'; + static VERSION = '2.2.5'; 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 */ @@ -165,7 +165,7 @@ class WallPlotter { // Public API — setup / UI // =========================================================================== - /** Enable keyboard jog, home, pen toggle, connect (C), stop (S). Call from setup(). */ + /** Enable keyboard jog, home, pen toggle, connect (C), test (T), stop (S). Call from setup(). */ enableKeyInputs() { if (this._keysEnabled) return; this._keysEnabled = true; @@ -588,13 +588,24 @@ class WallPlotter { const waitOk = options.waitOk !== false; const okPromise = waitOk - ? this._waitForOk(options.timeoutMs ?? WallPlotter.OK_TIMEOUT_MS) + ? this._waitForOk(options.timeoutMs ?? WallPlotter.OK_TIMEOUT_MS, { + command, + waitLogAfterMs: options.waitLogAfterMs, + }) : null; - await this.writer.write(new TextEncoder().encode(command + '\n')); - this.onLog('→ ' + command); - if (!okPromise) return; try { - await okPromise; + const written = this.writer.write( + new TextEncoder().encode(command + '\n') + ); + if (okPromise) { + await Promise.all([ + written.then(() => this.onLog('→ ' + command)), + okPromise, + ]); + } else { + await written; + this.onLog('→ ' + command); + } } catch (err) { if (err.name === 'AbortError') throw err; this._okIgnoreUntil = performance.now() + 400; @@ -818,6 +829,15 @@ class WallPlotter { } finally { this.feedRateTravel = savedFeedTravel; this.feedRateDraw = savedFeedDraw; + if (!testOnly && this.connected) { + try { + await this.penUp(); + } catch (_) { + /* port may be gone */ + } + } else { + this.isPenDown = false; + } this.isPlotting = false; this.isPaused = false; this._pauseStartedAt = 0; @@ -898,7 +918,7 @@ class WallPlotter { } async _animatePosition(fromX, fromY, cmd, duration) { - this.isPenDown = cmd.type === 'draw'; + if (this._plotTestOnly) this.isPenDown = cmd.type === 'draw'; const waitMs = Math.max(duration, 1); let elapsed = 0; let last = performance.now(); @@ -976,11 +996,11 @@ class WallPlotter { async _sendSegmentAndWait(cmd) { await this._sendSegment(cmd); + const motionMs = Math.round(this._lastSegmentMs); await this.send('M400', { - timeoutMs: Math.max( - WallPlotter.OK_TIMEOUT_MS, - Math.round(this._lastSegmentMs) + 20000 - ), + timeoutMs: Math.max(WallPlotter.OK_TIMEOUT_MS, motionMs + 20000), + // M400 is supposed to last as long as the move. Don't call that "stuck". + waitLogAfterMs: Math.max(WallPlotter.OK_WAIT_LOG_MS, motionMs + 1500), }); } @@ -988,9 +1008,15 @@ class WallPlotter { // Internals — serial I/O // =========================================================================== - _waitForOk(timeoutMs) { + _waitForOk(timeoutMs, options = {}) { + const command = options.command || ''; + const waitLogAfterMs = options.waitLogAfterMs ?? WallPlotter.OK_WAIT_LOG_MS; + const waitLabel = command ? ` (${command})` : ''; + return new Promise((resolve, reject) => { let settled = false; + let timer = null; + let waitLogTimer = null; let waitLogInterval = null; const finish = (fn, arg) => { @@ -1004,6 +1030,17 @@ class WallPlotter { fn(arg); }; + const armTimer = () => { + clearTimeout(timer); + timer = setTimeout(() => { + const err = new Error( + `timeout waiting for Marlin ok${waitLabel} (${Math.round(timeoutMs / 1000)}s silent)` + ); + err.name = 'TimeoutError'; + entry.reject(err); + }, timeoutMs); + }; + const entry = { resolve: () => { if (this.statusText === 'Waiting for plotter…') { @@ -1015,20 +1052,17 @@ class WallPlotter { finish(resolve); }, reject: (err) => finish(reject, err), + bump: () => { + if (!settled) armTimer(); + }, }; - const timer = setTimeout(() => { - entry.reject( - new Error( - `timeout waiting for Marlin ok (${Math.round(timeoutMs / 1000)}s)` - ) - ); - }, timeoutMs); + armTimer(); - const waitLogTimer = setTimeout(() => { + waitLogTimer = setTimeout(() => { if (settled) return; const started = performance.now(); - this.onLog('… waiting for Marlin ok'); + this.onLog(`… waiting for Marlin ok${waitLabel}`); if (this.statusText !== 'Waiting for plotter…') { this._statusBeforeOkWait = this.statusText; } @@ -1036,9 +1070,9 @@ class WallPlotter { this._redrawSketch(); waitLogInterval = setInterval(() => { const s = Math.round((performance.now() - started) / 1000); - this.onLog(`… still waiting for Marlin ok (${s}s)`); + this.onLog(`… still waiting for Marlin ok${waitLabel} (${s}s)`); }, WallPlotter.OK_WAIT_PING_MS); - }, WallPlotter.OK_WAIT_LOG_MS); + }, waitLogAfterMs); this._okResolvers.push(entry); }); @@ -1066,12 +1100,7 @@ class WallPlotter { const { value, done } = await plotter.reader.read(); if (done) break; plotter._rxBuf += new TextDecoder().decode(value); - const lines = plotter._rxBuf.split(/\r?\n/); - plotter._rxBuf = lines.pop(); - for (const line of lines) { - const trimmed = line.trim(); - if (trimmed) plotter._handleResponse(trimmed); - } + plotter._drainRxBuffer(); } } catch (err) { if (plotter.readLoopActive) { @@ -1090,9 +1119,28 @@ class WallPlotter { loop(); } + _drainRxBuffer() { + const lines = this._rxBuf.split(/\r?\n/); + this._rxBuf = lines.pop(); + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed) this._handleResponse(trimmed); + } + // USB-CDC sometimes delivers a complete "ok" without a trailing newline + // until the next chunk. Don't leave that sitting in the buffer. + const rest = this._rxBuf.trim(); + if (/^(echo:\s*)?ok\b/i.test(rest)) { + this._handleResponse(rest); + this._rxBuf = ''; + } + } + _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(); + } const now = performance.now(); if (now - this._lastBusyLogAt > 4000) { this._lastBusyLogAt = now; @@ -1101,14 +1149,14 @@ class WallPlotter { return; } this.onLog('← ' + line); - if (/^ok\b/i.test(line)) { + if (/^(echo:\s*)?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(); + if (next) { + next.resolve(); + } else if (performance.now() < this._okIgnoreUntil) { + this.onLog('← ok ignored (stale, no waiter)'); + } } this._parseMachineBounds(line); if (line.startsWith('X:')) { @@ -1176,6 +1224,10 @@ class WallPlotter { await this.stopPlot(); return false; } + if (k === 't') { + this._onTestButton(); + return false; + } if (!this._keysEnabled || !this.connected) return; @@ -1613,6 +1665,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)', + 'T run test (preview)', 'S stop sending (in-flight stroke still finishes)', 'C connect / disconnect', ];