serial osx fix?

This commit is contained in:
Martin Fuchs
2026-08-16 16:41:10 +02:00
parent cf5c60da38
commit c05cc43ccb
2 changed files with 119 additions and 15 deletions
+1 -1
View File
@@ -166,7 +166,7 @@ All **async**. Coordinate moves on Plot require home (`H`) first.
### `connect()`
Web Serial port picker, enable steppers (`M17`), pen up, query polargraph area (`M665` → machine overlay bounds), `M114`.
Web Serial port picker, enable steppers (`M17`), pen up, query polargraph area (`M665` → machine overlay bounds), `M114`. Reload/close releases the port (needed on macOS). If only one previously allowed port exists, it is reused.
### `disconnect()`
+118 -14
View File
@@ -18,7 +18,7 @@ class WallPlotter {
// Static constants
// ---------------------------------------------------------------------------
static VERSION = '2.2.2';
static VERSION = '2.2.3';
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 */
@@ -156,6 +156,9 @@ class WallPlotter {
this._updatePlotButtons();
userOnStatusChange(status);
};
this._unloadBound = false;
this._installUnloadHandler();
}
// ===========================================================================
@@ -394,8 +397,16 @@ class WallPlotter {
}
if (this.connected) return;
this.port = await navigator.serial.requestPort();
await this.port.open({ baudRate: this.baudRate });
let port;
try {
port = await this._acquirePort();
await this._openPort(port);
} catch (err) {
this.onLog('! ' + err.message);
port = await navigator.serial.requestPort();
await this._openPort(port);
}
this.port = port;
this.writer = this.port.writable.getWriter();
this._startReadLoop();
@@ -415,37 +426,81 @@ class WallPlotter {
}
}
async _acquirePort() {
const existing = await navigator.serial.getPorts();
if (existing.length === 1) return existing[0];
return navigator.serial.requestPort();
}
async _openPort(port) {
const opened = port.open({ baudRate: this.baudRate });
const timedOut = this._delay(3000).then(() => {
const err = new Error(
'serial open timeout — port still held after reload. Unplug USB or close other serial tabs.'
);
err.name = 'TimeoutError';
throw err;
});
try {
await Promise.race([opened, timedOut]);
} catch (err) {
try {
await port.close();
} catch (_) {
/* ignore */
}
if (typeof port.forget === 'function') {
try {
await port.forget();
} catch (_) {
/* ignore */
}
}
throw err;
}
}
/** Close the serial port. */
async disconnect() {
if (!this.connected) return;
if (!this.connected && !this.port) return;
this.readLoopActive = false;
this._cancelPendingOk();
this._rxBuf = '';
this._machineBusy = false;
if (this.reader) {
const reader = this.reader;
const writer = this.writer;
const port = this.port;
this.reader = null;
this.writer = null;
this.port = null;
if (reader) {
try {
await this.reader.cancel();
await reader.cancel();
} catch (_) {
/* already closed */
}
this.reader = null;
}
if (this.writer) {
try {
this.writer.releaseLock();
reader.releaseLock();
} catch (_) {
/* ignore */
}
this.writer = null;
}
if (this.port) {
if (writer) {
try {
await this.port.close();
writer.releaseLock();
} catch (_) {
/* ignore */
}
}
if (port) {
try {
await port.close();
} catch (_) {
/* ignore */
}
this.port = null;
}
this.connected = false;
@@ -454,6 +509,55 @@ class WallPlotter {
this.onStatusChange('disconnected');
}
/** Best-effort close for page reload (must not await — Mac keeps the USB lock otherwise). */
_releaseSerialNow() {
if (!this.port && !this.connected) 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.homeSet = false;
try {
reader?.cancel();
} catch (_) {
/* ignore */
}
try {
reader?.releaseLock();
} catch (_) {
/* ignore */
}
try {
writer?.releaseLock();
} catch (_) {
/* ignore */
}
try {
port?.close();
} catch (_) {
/* ignore */
}
}
_installUnloadHandler() {
if (this._unloadBound) return;
this._unloadBound = true;
const release = () => this._releaseSerialNow();
window.addEventListener('pagehide', release);
window.addEventListener('beforeunload', release);
window.addEventListener('unload', release);
window.addEventListener('pageshow', (event) => {
if (event.persisted) this._releaseSerialNow();
});
}
/**
* Write one G-code line; by default wait for Marlin ok.
* @param {string} line