From 2432ce3bd19c150214568886c3f295563b61c716 Mon Sep 17 00:00:00 2001 From: fraser Date: Mon, 22 Mar 2021 22:06:55 +0800 Subject: [PATCH 1/7] support passing own port/reusing existing port --- lib/browser-serialport.js | 9 +++++++-- lib/connection-browser.js | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/browser-serialport.js b/lib/browser-serialport.js index 4483dd1..6b3551b 100644 --- a/lib/browser-serialport.js +++ b/lib/browser-serialport.js @@ -9,7 +9,7 @@ class SerialPort extends EventEmitter { this.browser = true; this.path = this.options.path; this.isOpen = false; - this.port = null; + this.port = port || null; this.writer = null; this.reader = null; this.baudRate = this.options.baudRate; @@ -24,8 +24,13 @@ class SerialPort extends EventEmitter { .catch((error) => {if (callback) {return callback(error)}}); } + async _getPort() { + if (this.port && !this.options.forceRequest) return this.port; + return window.navigator.serial.requestPort(this.requestOptions) + } + open(callback) { - window.navigator.serial.requestPort(this.requestOptions) + this._getPort() .then(serialPort => { this.port = serialPort; if (this.isOpen) return; diff --git a/lib/connection-browser.js b/lib/connection-browser.js index 093e8e1..0c99d22 100644 --- a/lib/connection-browser.js +++ b/lib/connection-browser.js @@ -25,7 +25,7 @@ Connection.prototype._init = function(callback) { * Create new serialport instance for the Arduino board, but do not immediately connect. */ Connection.prototype._setUpSerial = function(callback) { - this.serialPort = new Serialport('', { + this.serialPort = new Serialport(this.options.port, { baudRate: this.board.baud, autoOpen: false }); From 8633f4381ed357283aa25c1bdfcec663cff16689 Mon Sep 17 00:00:00 2001 From: fraser Date: Mon, 22 Mar 2021 23:30:00 +0800 Subject: [PATCH 2/7] implement missing serialport methods/functionality --- lib/browser-serialport.js | 109 ++++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 23 deletions(-) diff --git a/lib/browser-serialport.js b/lib/browser-serialport.js index 6b3551b..3692819 100644 --- a/lib/browser-serialport.js +++ b/lib/browser-serialport.js @@ -12,6 +12,9 @@ class SerialPort extends EventEmitter { this.port = port || null; this.writer = null; this.reader = null; + this.isUpdating = false; + this.pausePromise = Promise.resolve(); + this.pauseResolve = () => {}; this.baudRate = this.options.baudRate; this.requestOptions = this.options.requestOptions || {}; @@ -30,31 +33,38 @@ class SerialPort extends EventEmitter { } open(callback) { - this._getPort() - .then(serialPort => { - this.port = serialPort; - if (this.isOpen) return; - return this.port.open({ baudRate: this.baudRate || 57600 }); - }) - .then(() => this.writer = this.port.writable.getWriter()) - .then(() => this.reader = this.port.readable.getReader()) - .then(async () => { - this.emit('open'); - this.isOpen = true; - callback(null); - while (this.port.readable.locked) { - try { - const { value, done } = await this.reader.read(); - if (done) { - break; + return new Promise((resolve, reject) => { + this._getPort() + .then(serialPort => { + this.port = serialPort; + if (this.isOpen) return; + return this.port.open({ baudRate: this.baudRate || 57600 }); + }) + .then(() => this.writer = this.port.writable.getWriter()) + .then(() => this.reader = this.port.readable.getReader()) + .then(async () => { + if (!this.isUpdating) this.emit('open'); + this.isOpen = true; + if (callback) callback(null); + resolve(null); + while (this.port.readable.locked) { + try { + await this.pausePromise; + const { value, done } = await this.reader.read(); + if (done) { + break; + } + this.emit('data', Buffer.from(value)); + } catch (e) { + console.error(e); } - this.emit('data', Buffer.from(value)); - } catch (e) { - console.error(e); } - } - }) - .catch(error => {callback(error)}); + }) + .catch(error => { + if (callback) callback(error); + reject(error); + }); + }); } async close(callback) { @@ -62,6 +72,7 @@ class SerialPort extends EventEmitter { await this.reader.releaseLock(); await this.writer.releaseLock(); await this.port.close(); + if (!this.isUpdating) this.emit('close'); this.isOpen = false; } catch (error) { if (callback) return callback(error); @@ -92,6 +103,27 @@ class SerialPort extends EventEmitter { if (callback) return callback(null); } + async get(callback) { + const props = {}; + try { + const signals = await this.port.getSignals(); + if (Object.prototype.hasOwnProperty.call(signals, 'dataCarrierDetect')) { + props.dcd = signals.dataCarrierDetect; + } + if (Object.prototype.hasOwnProperty.call(signals, 'clearToSend')) { + props.cts = signals.clearToSend; + } + if (Object.prototype.hasOwnProperty.call(signals, 'dataSetReady')) { + props.dsr = signals.dataSetReady; + } + } catch (error) { + if (callback) return callback(error); + throw error; + } + if (callback) return callback(props); + return props; + } + write(buffer, callback) { this.writer.write(buffer); if (callback) return callback(null); @@ -108,6 +140,37 @@ class SerialPort extends EventEmitter { if (callback) callback(null, buffer); } + async update (options, callback) { + try { + if (Object.prototype.hasOwnProperty.call(options, 'baudRate')) { + if (this.isOpen) { + // try to quietly close then open the port with the new baudRate + this.isUpdating = true; + await this.close(); + this.baudRate = options.baudRate; + await this.open(); + this.isUpdating = false; + } else { + this.baudRate = options.baudRate; + } + } + } catch (error) { + if (callback) return callback(error); + throw error; + } + if (callback) callback(null); + } + + pause() { + this.pausePromise = new Promise((resolve) => { + this.pauseResolve = resolve; + }); + } + + resume() { + this.pauseResolve(); + } + // TODO: is this correct? flush(callback) { //this.port.flush(); // is this sync or a promise? From 426e9029aca802b3c041bdd987872557e3d6b8ab Mon Sep 17 00:00:00 2001 From: fraser Date: Tue, 23 Mar 2021 00:00:55 +0800 Subject: [PATCH 3/7] silently return already closed port --- lib/browser-serialport.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/browser-serialport.js b/lib/browser-serialport.js index 3692819..aa4480b 100644 --- a/lib/browser-serialport.js +++ b/lib/browser-serialport.js @@ -68,6 +68,10 @@ class SerialPort extends EventEmitter { } async close(callback) { + if (!this.isOpen) { + if (callback) callback(null); + return; + } try { await this.reader.releaseLock(); await this.writer.releaseLock(); @@ -78,7 +82,7 @@ class SerialPort extends EventEmitter { if (callback) return callback(error); throw error; } - callback && callback(null); + if (callback) callback(null); } async set(props = {}, callback) { From f82ef391b9b5b212338836f8940dad4cceab8343 Mon Sep 17 00:00:00 2001 From: fraser Date: Tue, 23 Mar 2021 00:08:24 +0800 Subject: [PATCH 4/7] allow passing through existing serial setup --- lib/connection-browser.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/lib/connection-browser.js b/lib/connection-browser.js index 0c99d22..913a26f 100644 --- a/lib/connection-browser.js +++ b/lib/connection-browser.js @@ -25,14 +25,21 @@ Connection.prototype._init = function(callback) { * Create new serialport instance for the Arduino board, but do not immediately connect. */ Connection.prototype._setUpSerial = function(callback) { - this.serialPort = new Serialport(this.options.port, { - baudRate: this.board.baud, - autoOpen: false - }); - this.serialPort.on('open', function() { - // _this.emit('connection:open'); - }) - return callback(null); + if (this.options.serialPort) { + this.serialPort = this.options.serialPort; + this.serialPort.update({ baudRate: this.board.baud }, function(error) { + return callback(error); + }); + } else { + this.serialPort = new Serialport(this.options.port, { + baudRate: this.board.baud, + autoOpen: false + }); + this.serialPort.on('open', function() { + // _this.emit('connection:open'); + }) + return callback(null); + } }; /** From db8dff1f822311e5a7d258c79066b653549bb827 Mon Sep 17 00:00:00 2001 From: fraser Date: Tue, 23 Mar 2021 00:09:13 +0800 Subject: [PATCH 5/7] skip updating baudRate if it's the same --- lib/browser-serialport.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/browser-serialport.js b/lib/browser-serialport.js index aa4480b..8213bec 100644 --- a/lib/browser-serialport.js +++ b/lib/browser-serialport.js @@ -146,7 +146,10 @@ class SerialPort extends EventEmitter { async update (options, callback) { try { - if (Object.prototype.hasOwnProperty.call(options, 'baudRate')) { + if ( + Object.prototype.hasOwnProperty.call(options, 'baudRate') + && this.baudRate !== options.baudRate + ) { if (this.isOpen) { // try to quietly close then open the port with the new baudRate this.isUpdating = true; From 18c6b1bdd2caeed3260d16a2ef8c9b9077734c5b Mon Sep 17 00:00:00 2001 From: fraser Date: Wed, 24 Mar 2021 00:50:44 +0800 Subject: [PATCH 6/7] ensure reader isn't reading when closing the port --- avrgirl-arduino.js | 1 + lib/browser-serialport.js | 13 ++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/avrgirl-arduino.js b/avrgirl-arduino.js index 96f0906..26df0d8 100644 --- a/avrgirl-arduino.js +++ b/avrgirl-arduino.js @@ -15,6 +15,7 @@ var injectDependencies = function(boards, Connection, protocols) { megaDebug: opts.megaDebug || false, board: opts.board || 'uno', port: opts.port || '', + serialPort: opts.serialPort || null, manualReset: opts.manualReset || false, disableVerify: opts.disableVerify || false }; diff --git a/lib/browser-serialport.js b/lib/browser-serialport.js index 8213bec..8afbd78 100644 --- a/lib/browser-serialport.js +++ b/lib/browser-serialport.js @@ -15,6 +15,8 @@ class SerialPort extends EventEmitter { this.isUpdating = false; this.pausePromise = Promise.resolve(); this.pauseResolve = () => {}; + this.closeReadPromise = null; + this.closeReadResolve = () => {}; this.baudRate = this.options.baudRate; this.requestOptions = this.options.requestOptions || {}; @@ -51,7 +53,7 @@ class SerialPort extends EventEmitter { try { await this.pausePromise; const { value, done } = await this.reader.read(); - if (done) { + if (done || this.closeReadPromise) { break; } this.emit('data', Buffer.from(value)); @@ -59,6 +61,7 @@ class SerialPort extends EventEmitter { console.error(e); } } + this.closeReadResolve(); }) .catch(error => { if (callback) callback(error); @@ -72,6 +75,14 @@ class SerialPort extends EventEmitter { if (callback) callback(null); return; } + if (this.port.readable.locked) { + this.closeReadPromise = new Promise((resolve) => { + this.closeReadResolve = resolve; + }); + this.pauseResolve(); + await this.closeReadPromise; + this.closeReadPromise = null; + } try { await this.reader.releaseLock(); await this.writer.releaseLock(); From 5b98ed4d2da692c9f52361b9f8f77ac04ce171d1 Mon Sep 17 00:00:00 2001 From: mrfrase3 Date: Sat, 24 Apr 2021 14:26:05 +0800 Subject: [PATCH 7/7] fix hanging when device isn't using serial --- lib/browser-serialport.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/browser-serialport.js b/lib/browser-serialport.js index 8afbd78..998e8dc 100644 --- a/lib/browser-serialport.js +++ b/lib/browser-serialport.js @@ -80,6 +80,9 @@ class SerialPort extends EventEmitter { this.closeReadResolve = resolve; }); this.pauseResolve(); + try { + await this.reader.cancel(); + } catch (err) { console.error(err); } await this.closeReadPromise; this.closeReadPromise = null; }