serial osx fix?
This commit is contained in:
+1
-1
@@ -166,7 +166,7 @@ All **async**. Coordinate moves on Plot require home (`H`) first.
|
|||||||
|
|
||||||
### `connect()`
|
### `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()`
|
### `disconnect()`
|
||||||
|
|
||||||
|
|||||||
+118
-14
@@ -18,7 +18,7 @@ class WallPlotter {
|
|||||||
// Static constants
|
// Static constants
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
static VERSION = '2.2.2';
|
static VERSION = '2.2.3';
|
||||||
static MIN_FEED_MM_MIN = 1;
|
static MIN_FEED_MM_MIN = 1;
|
||||||
static MAX_FEED_MM_MIN = 10000;
|
static MAX_FEED_MM_MIN = 10000;
|
||||||
/** Offline overlay size when not connected (mm) — 150×150 cm centred on origin */
|
/** Offline overlay size when not connected (mm) — 150×150 cm centred on origin */
|
||||||
@@ -156,6 +156,9 @@ class WallPlotter {
|
|||||||
this._updatePlotButtons();
|
this._updatePlotButtons();
|
||||||
userOnStatusChange(status);
|
userOnStatusChange(status);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this._unloadBound = false;
|
||||||
|
this._installUnloadHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
@@ -394,8 +397,16 @@ class WallPlotter {
|
|||||||
}
|
}
|
||||||
if (this.connected) return;
|
if (this.connected) return;
|
||||||
|
|
||||||
this.port = await navigator.serial.requestPort();
|
let port;
|
||||||
await this.port.open({ baudRate: this.baudRate });
|
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.writer = this.port.writable.getWriter();
|
||||||
this._startReadLoop();
|
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. */
|
/** Close the serial port. */
|
||||||
async disconnect() {
|
async disconnect() {
|
||||||
if (!this.connected) return;
|
if (!this.connected && !this.port) return;
|
||||||
|
|
||||||
this.readLoopActive = false;
|
this.readLoopActive = false;
|
||||||
this._cancelPendingOk();
|
this._cancelPendingOk();
|
||||||
this._rxBuf = '';
|
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 {
|
try {
|
||||||
await this.reader.cancel();
|
await reader.cancel();
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
/* already closed */
|
/* already closed */
|
||||||
}
|
}
|
||||||
this.reader = null;
|
|
||||||
}
|
|
||||||
if (this.writer) {
|
|
||||||
try {
|
try {
|
||||||
this.writer.releaseLock();
|
reader.releaseLock();
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
this.writer = null;
|
|
||||||
}
|
}
|
||||||
if (this.port) {
|
if (writer) {
|
||||||
try {
|
try {
|
||||||
await this.port.close();
|
writer.releaseLock();
|
||||||
|
} catch (_) {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (port) {
|
||||||
|
try {
|
||||||
|
await port.close();
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
}
|
}
|
||||||
this.port = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connected = false;
|
this.connected = false;
|
||||||
@@ -454,6 +509,55 @@ class WallPlotter {
|
|||||||
this.onStatusChange('disconnected');
|
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.
|
* Write one G-code line; by default wait for Marlin ok.
|
||||||
* @param {string} line
|
* @param {string} line
|
||||||
|
|||||||
Reference in New Issue
Block a user