diff --git a/src/js/serial_backend.js b/src/js/serial_backend.js index 9b8de7bfb7..79b20c10a7 100644 --- a/src/js/serial_backend.js +++ b/src/js/serial_backend.js @@ -27,13 +27,27 @@ import BuildApi from "./BuildApi"; import serialNWJS from "./serial.js"; import serialWeb from "./webSerial.js"; -const serial = import.meta.env ? serialWeb : serialNWJS; +let serial = import.meta.env ? serialWeb : serialNWJS; let mspHelper; let connectionTimestamp; -let clicks = false; let liveDataRefreshTimerId = false; +let isConnected = false; + +const toggleStatus = function () { + isConnected = !isConnected; +}; + +function connectHandler(event) { + onOpen(event.detail); + toggleStatus(); +} + +function disconnectHandler(event) { + onClosed(event.detail); +} + export function initializeSerialBackend() { GUI.updateManualPortVisibility = function() { const selected_port = $('div#port-picker #port option:selected'); @@ -82,10 +96,6 @@ export function initializeSerialBackend() { if (!GUI.connect_lock) { // GUI control overrides the user control - const toggleStatus = function () { - clicks = !clicks; - }; - GUI.configuration_loaded = false; const selected_baud = parseInt($('div#port-picker #baud').val()); @@ -94,7 +104,7 @@ export function initializeSerialBackend() { if (selectedPort.data().isDFU) { $('select#baud').hide(); } else if (portName !== '0') { - if (!clicks) { + if (!isConnected) { console.log(`Connecting to: ${portName}`); GUI.connecting_to = portName; @@ -109,10 +119,13 @@ export function initializeSerialBackend() { serial.connect('virtual', {}, onOpenVirtual); } else if (import.meta.env) { - serial.addEventListener('connect', (event) => { - onOpen(event.detail); - toggleStatus(); - }); + // Explicitly disconnect the event listeners before attaching the new ones. + serial.removeEventListener('connect', connectHandler); + serial.addEventListener('connect', connectHandler); + + serial.removeEventListener('disconnect', disconnectHandler); + serial.addEventListener('disconnect', disconnectHandler); + serial.connect({ baudRate }); } else { serial.connect( @@ -276,7 +289,15 @@ function abortConnection() { $('div#port-picker #port, div#port-picker #baud, div#port-picker #delay').prop('disabled', false); // reset data - clicks = false; + isConnected = false; +} + +/** + * purpose of this is to bridge the old and new api + * when serial events are handled. + */ +function read_serial_adapter(event) { + read_serial(event.detail.buffer); } function onOpen(openInfo) { @@ -307,7 +328,8 @@ function onOpen(openInfo) { $('input[name="expertModeCheckbox"]').prop('checked', result).trigger('change'); if(import.meta.env) { - serial.addEventListener('receive', (e) => read_serial(e.detail.buffer)); + serial.removeEventListener('receive', read_serial_adapter); + serial.addEventListener('receive', read_serial_adapter); } else { serial.onReceive.addListener(read_serial); } diff --git a/src/js/webSerial.js b/src/js/webSerial.js index 90fa677292..9d0317335e 100644 --- a/src/js/webSerial.js +++ b/src/js/webSerial.js @@ -1,9 +1,8 @@ import { webSerialDevices } from "./serial_devices"; -async function* streamAsyncIterable(stream) { - const reader = stream.getReader(); +async function* streamAsyncIterable(reader, keepReadingFlag) { try { - while (true) { + while (keepReadingFlag()) { const { done, value } = await reader.read(); if (done) { return; @@ -34,10 +33,20 @@ class WebSerial extends EventTarget { this.port = null; this.reader = null; this.writer = null; + this.reading = false; this.connect = this.connect.bind(this); } + handleReceiveBytes(info) { + this.bytesReceived += info.detail.byteLength; + } + + handleDisconnect() { + this.removeEventListener('receive', this.handleReceiveBytes); + this.removeEventListener('disconnect', this.handleDisconnect); + } + async connect(options) { this.openRequested = true; this.port = await navigator.serial.requestPort({ @@ -48,6 +57,7 @@ class WebSerial extends EventTarget { const connectionInfo = this.port.getInfo(); this.connectionInfo = connectionInfo; this.writer = this.port.writable.getWriter(); + this.reader = this.port.readable.getReader(); if (connectionInfo && !this.openCanceled) { this.connected = true; @@ -58,9 +68,8 @@ class WebSerial extends EventTarget { this.failed = 0; this.openRequested = false; - this.addEventListener("receive", (info) => { - this.bytesReceived += info.detail.byteLength; - }); + this.addEventListener("receive", this.handleReceiveBytes); + this.addEventListener('disconnect', this.handleDisconnect); console.log( `${this.logHead} Connection opened with ID: ${connectionInfo.connectionId}, Baud: ${options.baudRate}`, @@ -73,7 +82,9 @@ class WebSerial extends EventTarget { // the stream async iterable interface: // https://web.dev/streams/#asynchronous-iteration - for await (let value of streamAsyncIterable(this.port.readable)) { + + this.reading = true; + for await (let value of streamAsyncIterable(this.reader, () => this.reading)) { this.dispatchEvent( new CustomEvent("receive", { detail: value }), ); @@ -108,32 +119,46 @@ class WebSerial extends EventTarget { async disconnect() { this.connected = false; + this.transmitting = false; + this.reading = false; + this.bytesReceived = 0; + this.bytesSent = 0; - if (this.port) { - this.transmitting = false; + const doCleanup = async () => { + if (this.reader) { + this.reader.releaseLock(); + this.reader = null; + } if (this.writer) { - await this.writer.close(); + await this.writer.releaseLock(); this.writer = null; } - try { + if (this.port) { await this.port.close(); this.port = null; + } + }; - console.log( - `${this.logHead}Connection with ID: ${this.connectionId} closed, Sent: ${this.bytesSent} bytes, Received: ${this.bytesReceived} bytes`, - ); + try { + await doCleanup(); - this.connectionId = false; - this.bitrate = 0; - this.dispatchEvent(new CustomEvent("disconnect")); - } catch (error) { - console.error(error); - console.error( - `${this.logHead}Failed to close connection with ID: ${this.connectionId} closed, Sent: ${this.bytesSent} bytes, Received: ${this.bytesReceived} bytes`, - ); + console.log( + `${this.logHead}Connection with ID: ${this.connectionId} closed, Sent: ${this.bytesSent} bytes, Received: ${this.bytesReceived} bytes`, + ); + + this.connectionId = false; + this.bitrate = 0; + this.dispatchEvent(new CustomEvent("disconnect", { detail: true })); + } catch (error) { + console.error(error); + console.error( + `${this.logHead}Failed to close connection with ID: ${this.connectionId} closed, Sent: ${this.bytesSent} bytes, Received: ${this.bytesReceived} bytes`, + ); + this.dispatchEvent(new CustomEvent("disconnect", { detail: false })); + } finally { + if (this.openCanceled) { + this.openCanceled = false; } - } else { - this.openCanceled = true; } }