From ed9a91afdc44f0aecc8460383e56fd7d63dc44ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E7=AB=8B=E5=B8=AE?= <3294713004@qq.com> Date: Fri, 24 Oct 2025 16:07:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E6=94=AF=E6=8C=81=E9=80=9A?= =?UTF-8?q?=E8=BF=87web=20usb=E7=83=A7=E5=BD=95=E5=9B=BA=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/modules/mixly-modules/common/app.js | 6 +- common/modules/mixly-modules/common/serial.js | 23 + common/modules/mixly-modules/deps.json | 21 +- .../modules/mixly-modules/web/burn-upload.js | 18 +- .../mixly-modules/web/hid-transport.js | 418 ------------------ common/modules/mixly-modules/web/hid.js | 9 + .../mixly-modules/web/serial-transport.js | 221 +++++++++ .../modules/mixly-modules/web/serialport.js | 11 + common/modules/mixly-modules/web/usb-mini.js | 9 + common/modules/mixly-modules/web/usb.js | 9 + 10 files changed, 307 insertions(+), 438 deletions(-) delete mode 100644 common/modules/mixly-modules/web/hid-transport.js create mode 100644 common/modules/mixly-modules/web/serial-transport.js diff --git a/common/modules/mixly-modules/common/app.js b/common/modules/mixly-modules/common/app.js index 92b2f60a..6caefc9c 100644 --- a/common/modules/mixly-modules/common/app.js +++ b/common/modules/mixly-modules/common/app.js @@ -249,10 +249,10 @@ class App extends Component { if (goog.isElectron || Env.hasSocketServer) { return SELECTED_BOARD?.nav?.burn; } - if (Serial.devicesRegistry.hasKey('serial')) { - return SELECTED_BOARD?.nav?.burn; - } else { + if (Serial.devicesRegistry.hasKey('hid')) { return false; + } else { + return SELECTED_BOARD?.nav?.burn; } }, callback: () => BU.initBurn(), diff --git a/common/modules/mixly-modules/common/serial.js b/common/modules/mixly-modules/common/serial.js index d950b924..9a7df1d2 100644 --- a/common/modules/mixly-modules/common/serial.js +++ b/common/modules/mixly-modules/common/serial.js @@ -110,6 +110,8 @@ class Serial { #baud_ = 0; #dtr_ = false; #rts_ = false; + #vid_ = 0x0000; + #pid_ = 0x0000; #isOpened_ = false; #port_ = ''; #special_ = []; @@ -250,6 +252,19 @@ class Serial { this.#rts_ = rts; } + async setVID(vid) { + this.#vid_ = vid; + } + + async setPID(pid) { + this.#pid_ = pid; + } + + async setVIDAndPID(vid, pid) { + this.#vid_ = vid; + this.#pid_ = pid; + } + getPortName() { return this.#port_; } @@ -270,6 +285,14 @@ class Serial { return this.#rts_; } + getVID() { + return this.#vid_; + } + + getPID() { + return this.#pid_; + } + async sendString(str) {} async sendBuffer(buffer) {} diff --git a/common/modules/mixly-modules/deps.json b/common/modules/mixly-modules/deps.json index 402e4114..ad4cf658 100644 --- a/common/modules/mixly-modules/deps.json +++ b/common/modules/mixly-modules/deps.json @@ -1600,7 +1600,8 @@ "Mixly.LayerFirmware", "Mixly.LayerProgress", "Mixly.Web.Serial", - "Mixly.Web.Ampy" + "Mixly.Web.Ampy", + "Mixly.Web.SerialTransport" ], "provide": [ "Mixly.Web.BU" @@ -1656,15 +1657,6 @@ "Mixly.Web.FS" ] }, - { - "path": "/web/hid-transport.js", - "require": [ - "Mixly.Web" - ], - "provide": [ - "Mixly.Web.HIDTransport" - ] - }, { "path": "/web/hid.js", "require": [ @@ -1676,6 +1668,15 @@ "Mixly.Web.HID" ] }, + { + "path": "/web/serial-transport.js", + "require": [ + "Mixly.Web" + ], + "provide": [ + "Mixly.Web.SerialTransport" + ] + }, { "path": "/web/serial.js", "require": [ diff --git a/common/modules/mixly-modules/web/burn-upload.js b/common/modules/mixly-modules/web/burn-upload.js index 9d5bfffc..14c8ade5 100644 --- a/common/modules/mixly-modules/web/burn-upload.js +++ b/common/modules/mixly-modules/web/burn-upload.js @@ -21,6 +21,7 @@ goog.require('Mixly.LayerFirmware'); goog.require('Mixly.LayerProgress'); goog.require('Mixly.Web.Serial'); goog.require('Mixly.Web.Ampy'); +goog.require('Mixly.Web.SerialTransport'); goog.provide('Mixly.Web.BU'); const { @@ -42,11 +43,13 @@ const { const { Serial, BU, - Ampy + Ampy, + SerialTransport } = Web; const { BOARD, SELECTED_BOARD } = Config; -const { ESPLoader, Transport } = esptooljs; +const { ESPLoader } = esptooljs; + BU.uploading = false; BU.burning = false; @@ -246,7 +249,7 @@ BU.burnWithEsptool = async (binFile, erase) => { } } const port = Serial.getPort(portName); - if (['HIDDevice', 'USBDevice'].includes(port.constructor.name)) { + if (['HIDDevice'].includes(port.constructor.name)) { layer.msg(Msg.Lang['burn.notSupport'], { time: 1000 }); return; } @@ -264,13 +267,14 @@ BU.burnWithEsptool = async (binFile, erase) => { let transport = null; try { const baudrate = Boards.getSelectedBoardConfigParam('BurnSpeed') ?? '460800'; - transport = new Transport(port, false); + const serial = new Serial(portName); + transport = new SerialTransport(serial, false); esploader = new ESPLoader({ transport, baudrate, terminal: { clean() { - statusBarTerminal.setValue(''); + statusBarTerminal.setValue(Msg.Lang['shell.burning'] + '...\n'); }, writeLine(data) { statusBarTerminal.addValue(data + '\n'); @@ -290,6 +294,7 @@ BU.burnWithEsptool = async (binFile, erase) => { Debug.error(error); } BU.progressLayer.hide(); + statusBarTerminal.addValue(`==${Msg.Lang['shell.burnFailed']}==\n`); return; } @@ -299,8 +304,6 @@ BU.burnWithEsptool = async (binFile, erase) => { for (let i of binFile) { if (i.path && i.offset) { let absolutePath = path.join(Env.boardDirPath, i.path); - // statusBarTerminal.addValue(`${Msg.Lang['读取固件'] + ' ' - // + Msg.Lang['路径']}:${absolutePath}, ${Msg.Lang['偏移']}:${i.offset}\n`); firmwarePromise.push(readBinFile(absolutePath, i.offset)); } } @@ -316,6 +319,7 @@ BU.burnWithEsptool = async (binFile, erase) => { Debug.error(error); } BU.progressLayer.hide(); + statusBarTerminal.addValue(`==${Msg.Lang['shell.burnFailed']}==\n`); return; } statusBarTerminal.addValue("Done!\n"); diff --git a/common/modules/mixly-modules/web/hid-transport.js b/common/modules/mixly-modules/web/hid-transport.js deleted file mode 100644 index 45418d58..00000000 --- a/common/modules/mixly-modules/web/hid-transport.js +++ /dev/null @@ -1,418 +0,0 @@ -goog.loadJs('web', () => { - -goog.require('Mixly.Web'); -goog.provide('Mixly.Web.HIDTransport'); - -const { Web } = Mixly; - -class Transport { - slipReaderEnabled = false; - baudrate = 0; - traceLog = ""; - lastTraceTime = Date.now(); - finished = false; - buffer = new Uint8Array(0); - device = null; - tracing = false; - received = new Uint8Array(0); - - constructor(device, tracing, enableSlipReader) { - this.slipReaderEnabled = enableSlipReader; - this.device = device; - this.tracing = tracing; - } - - async sendBuffer(buffer) { - for (let i = 0; i < buffer.length / 31; i++) { - const len = Math.min((i + 1) * 31, buffer.length) - i * 31; - let temp = new Uint8Array(len + 1); - temp[0] = len; - temp.set(buffer.subarray(i * 31, Math.min((i + 1) * 31, buffer.length)), 1); - await this.device.sendReport(0, temp); - } - // if (buffer instanceof Uint8Array) { - // let temp = new Uint8Array(buffer.length + 1); - // temp[0] = buffer.length; - // temp.set(buffer, 1); - // buffer = temp; - // } else { - // buffer.unshift(buffer.length); - // buffer = new Uint8Array(buffer); - // } - } - - #addEventsListener_() { - this.device.oninputreport = (event) => { - const { data } = event; - const length = Math.min(data.getUint8(0), data.byteLength); - let buffer = []; - for (let i = 1; i <= length; i++) { - buffer.push(data.getUint8(i)); - } - this.received = this.appendArray(this.received, new Uint8Array(buffer)); - }; - - // this.device.onclose = () => {} - } - - async readOrigin(timeout) { - let time = Date.now(); - let output = { - done: false, - value: null - }; - while (Date.now() - time < timeout && !this.finished) { - if (this.received.byteLength) { - output.value = this.received; - this.received = new Uint8Array(0); - break; - } else { - await this.sleep(5); - } - } - if (!output.value) { - output.value = new Uint8Array(0); - } - output.done = this.finished; - return output; - } - - /** - * Request the serial device vendor ID and Product ID as string. - * @returns {string} Return the device VendorID and ProductID from SerialPortInfo as formatted string. - */ - getInfo() { - const { vendorId, productId } = this.device; - return vendorId && productId - ? `WebHID VendorID 0x${vendorId.toString(16)} ProductID 0x${productId.toString(16)}` - : ""; - } - - /** - * Request the serial device product id from SerialPortInfo. - * @returns {number | undefined} Return the product ID. - */ - getPid() { - return this.device.productId; - } - - /** - * Format received or sent data for tracing output. - * @param {string} message Message to format as trace line. - */ - trace(message) { - if (!this.tracing) { - return; - } - const delta = Date.now() - this.lastTraceTime; - const prefix = `TRACE ${delta.toFixed(3)}`; - const traceMessage = `${prefix} ${message}`; - console.log(traceMessage); - this.traceLog += traceMessage + "\n"; - } - - async returnTrace() { - try { - await navigator.clipboard.writeText(this.traceLog); - console.log("Text copied to clipboard!"); - } catch (err) { - console.error("Failed to copy text:", err); - } - } - - hexify(s) { - return Array.from(s) - .map((byte) => byte.toString(16).padStart(2, "0")) - .join("") - .padEnd(16, " "); - } - - hexConvert(uint8Array, autoSplit) { - if (autoSplit && uint8Array.length > 16) { - let result = ""; - let s = uint8Array; - - while (s.length > 0) { - const line = s.slice(0, 16); - const asciiLine = String.fromCharCode(...line) - .split("") - .map((c) => (c === " " || (c >= " " && c <= "~" && c !== " ") ? c : ".")) - .join(""); - s = s.slice(16); - result += `\n ${this.hexify(line.slice(0, 8))} ${this.hexify(line.slice(8))} | ${asciiLine}`; - } - - return result; - } else { - return this.hexify(uint8Array); - } - } - - /** - * Format data packet using the Serial Line Internet Protocol (SLIP). - * @param {Uint8Array} data Binary unsigned 8 bit array data to format. - * @returns {Uint8Array} Formatted unsigned 8 bit data array. - */ - slipWriter(data) { - const outData = []; - outData.push(0xc0); - for (let i = 0; i < data.length; i++) { - if (data[i] === 0xdb) { - outData.push(0xdb, 0xdd); - } else if (data[i] === 0xc0) { - outData.push(0xdb, 0xdc); - } else { - outData.push(data[i]); - } - } - outData.push(0xc0); - return new Uint8Array(outData); - } - - /** - * Write binary data to device using the WebSerial device writable stream. - * @param {Uint8Array} data 8 bit unsigned data array to write to device. - */ - async write(data) { - const outData = this.slipWriter(data); - if (this.tracing) { - console.log("Write bytes"); - this.trace(`Write ${outData.length} bytes: ${this.hexConvert(outData)}`); - } - await this.sendBuffer(outData); - } - - /** - * Append a buffer array after another buffer array - * @param {Uint8Array} arr1 - First array buffer. - * @param {Uint8Array} arr2 - magic hex number to select ROM. - * @returns {Uint8Array} Return a 8 bit unsigned array. - */ - appendArray(arr1, arr2) { - const combined = new Uint8Array(arr1.length + arr2.length); - combined.set(arr1); - combined.set(arr2, arr1.length); - return combined; - } - - // Asynchronous generator to yield incoming data chunks - async *readLoop(timeout) { - try { - while (true) { - // Await the race between the timeout and the reader read - const result = await this.readOrigin(timeout); - - // If a timeout occurs, result will be null; otherwise, it will have { value, done } - if (result === null) break; - - const { value, done } = result; - - if (done || !value) break; - - yield value; // Yield each data chunk - } - } catch (error) { - console.error("Error reading from serial port:", error); - } finally { - this.buffer = new Uint8Array(0); - } - } - - // Read a specific number of bytes - async newRead(numBytes, timeout) { - if (this.buffer.length >= numBytes) { - const output = this.buffer.slice(0, numBytes); - this.buffer = this.buffer.slice(numBytes); // Remove the returned data from buffer - return output; - } - while (this.buffer.length < numBytes) { - const readLoop = this.readLoop(timeout); - const { value, done } = await readLoop.next(); - - if (done || !value) { - break; - } - - // Append the newly read data to the buffer - this.buffer = this.appendArray(this.buffer, value); - } - - // Return as much data as possible - const output = this.buffer.slice(0, numBytes); - this.buffer = this.buffer.slice(numBytes); - - return output; - } - - async flushInput() {} - - async flushOutput() { - this.buffer = new Uint8Array(0); - } - - // `inWaiting` returns the count of bytes in the buffer - inWaiting() { - return this.buffer.length; - } - - /** - * Detect if the data read from device is a Fatal or Guru meditation error. - * @param {Uint8Array} input Data read from device - */ - detectPanicHandler(input) { - const guruMeditationRegex = /G?uru Meditation Error: (?:Core \d panic'ed \(([a-zA-Z ]*)\))?/; - const fatalExceptionRegex = /F?atal exception \(\d+\): (?:([a-zA-Z ]*)?.*epc)?/; - - const inputString = new TextDecoder("utf-8").decode(input); - const match = inputString.match(guruMeditationRegex) || inputString.match(fatalExceptionRegex); - - if (match) { - const cause = match[1] || match[2]; - const msg = `Guru Meditation Error detected${cause ? ` (${cause})` : ""}`; - throw new Error(msg); - } - } - - SLIP_END = 0xc0; - SLIP_ESC = 0xdb; - SLIP_ESC_END = 0xdc; - SLIP_ESC_ESC = 0xdd; - - /** - * Take a data array and return the first well formed packet after - * replacing the escape sequence. Reads at least 8 bytes. - * @param {number} timeout Timeout read data. - * @yields {Uint8Array} Formatted packet using SLIP escape sequences. - */ - async *read(timeout) { - let partialPacket = null; - let isEscaping = false; - let successfulSlip = false; - - while (true) { - const waitingBytes = this.inWaiting(); - const readBytes = await this.newRead(waitingBytes > 0 ? waitingBytes : 1, timeout); - - if (!readBytes || readBytes.length === 0) { - const msg = - partialPacket === null - ? successfulSlip - ? "Serial data stream stopped: Possible serial noise or corruption." - : "No serial data received." - : `Packet content transfer stopped`; - this.trace(msg); - throw new Error(msg); - } - - this.trace(`Read ${readBytes.length} bytes: ${this.hexConvert(readBytes)}`); - - let i = 0; // Track position in readBytes - while (i < readBytes.length) { - const byte = readBytes[i++]; - if (partialPacket === null) { - if (byte === this.SLIP_END) { - partialPacket = new Uint8Array(0); // Start of a new packet - } else { - this.trace(`Read invalid data: ${this.hexConvert(readBytes)}`); - const remainingData = await this.newRead(this.inWaiting(), timeout); - this.trace(`Remaining data in serial buffer: ${this.hexConvert(remainingData)}`); - this.detectPanicHandler(new Uint8Array([...readBytes, ...(remainingData || [])])); - throw new Error(`Invalid head of packet (0x${byte.toString(16)}): Possible serial noise or corruption.`); - } - } else if (isEscaping) { - isEscaping = false; - if (byte === this.SLIP_ESC_END) { - partialPacket = this.appendArray(partialPacket, new Uint8Array([this.SLIP_END])); - } else if (byte === this.SLIP_ESC_ESC) { - partialPacket = this.appendArray(partialPacket, new Uint8Array([this.SLIP_ESC])); - } else { - this.trace(`Read invalid data: ${this.hexConvert(readBytes)}`); - const remainingData = await this.newRead(this.inWaiting(), timeout); - this.trace(`Remaining data in serial buffer: ${this.hexConvert(remainingData)}`); - this.detectPanicHandler(new Uint8Array([...readBytes, ...(remainingData || [])])); - throw new Error(`Invalid SLIP escape (0xdb, 0x${byte.toString(16)})`); - } - } else if (byte === this.SLIP_ESC) { - isEscaping = true; - } else if (byte === this.SLIP_END) { - this.trace(`Received full packet: ${this.hexConvert(partialPacket)}`); - this.buffer = this.appendArray(this.buffer, readBytes.slice(i)); - yield partialPacket; - partialPacket = null; - successfulSlip = true; - } else { - partialPacket = this.appendArray(partialPacket, new Uint8Array([byte])); - } - } - } - } - - _DTR_state = false; - /** - * Send the RequestToSend (RTS) signal to given state - * # True for EN=LOW, chip in reset and False EN=HIGH, chip out of reset - * @param {boolean} state Boolean state to set the signal - */ - async setRTS(state) { - // # Work-around for adapters on Windows using the usbser.sys driver: - // # generate a dummy change to DTR so that the set-control-line-state - // # request is sent with the updated RTS state and the same DTR state - // Referenced to esptool.py - await this.setDTR(this._DTR_state); - } - - /** - * Send the dataTerminalReady (DTS) signal to given state - * # True for IO0=LOW, chip in reset and False IO0=HIGH - * @param {boolean} state Boolean state to set the signal - */ - async setDTR(state) { - this._DTR_state = state; - } - - /** - * Connect to serial device using the Webserial open method. - * @param {number} baud Number baud rate for serial connection. Default is 115200. - * @param {typeof import("w3c-web-serial").SerialOptions} serialOptions Serial Options for WebUSB SerialPort class. - */ - async connect(baud = 115200, serialOptions = {}) { - // await this.device.open({ - // baudRate: baud, - // dataBits: serialOptions?.dataBits, - // stopBits: serialOptions?.stopBits, - // bufferSize: serialOptions?.bufferSize, - // parity: serialOptions?.parity, - // flowControl: serialOptions?.flowControl, - // }); - await this.device.open(); - this.finished = false; - this.#addEventsListener_(); - this.baudrate = baud; - } - - async sleep(ms) { - return new Promise((resolve) => setTimeout(resolve, ms)); - } - - /** - * Wait for a given timeout ms for serial device unlock. - * @param {number} timeout Timeout time in milliseconds (ms) to sleep - */ - async waitForUnlock(timeout) {} - - /** - * Disconnect from serial device by running SerialPort.close() after streams unlock. - */ - async disconnect() { - await this.waitForUnlock(400); - this.finished = true; - this.device.oninputreport = null; - this.device.onclose = null; - await this.device.close(); - } -} - - -Web.HIDTransport = Transport; - -}); \ No newline at end of file diff --git a/common/modules/mixly-modules/web/hid.js b/common/modules/mixly-modules/web/hid.js index fcaa4024..c739fe87 100644 --- a/common/modules/mixly-modules/web/hid.js +++ b/common/modules/mixly-modules/web/hid.js @@ -113,6 +113,7 @@ class WebHID extends Serial { #dataLength_ = 31; constructor(port) { super(port); + this.#device_ = WebHID.getPort(port); } #addEventsListener_() { @@ -212,6 +213,14 @@ class WebHID extends Serial { return this.setDTRAndRTS(this.getDTR(), rts); } + getVID() { + return this.#device_.vendorId; + } + + getPID() { + return this.#device_.productId; + } + onBuffer(buffer) { super.onBuffer(buffer); for (let i = 0; i < buffer.length; i++) { diff --git a/common/modules/mixly-modules/web/serial-transport.js b/common/modules/mixly-modules/web/serial-transport.js new file mode 100644 index 00000000..6f4d1726 --- /dev/null +++ b/common/modules/mixly-modules/web/serial-transport.js @@ -0,0 +1,221 @@ +goog.loadJs('web', () => { + +goog.require('Mixly.Web'); +goog.provide('Mixly.Web.SerialTransport'); + +const { Web } = Mixly; + + +class Transport { + slipReaderEnabled = false; + baudrate = 0; + traceLog = ""; + lastTraceTime = Date.now(); + buffer = new Uint8Array(0); + serial = null; + tracing = false; + + constructor(serial, tracing = false, enableSlipReader = true) { + this.serial = serial; + this.tracing = tracing; + this.slipReaderEnabled = enableSlipReader; + + this.serial.bind('onBuffer', (data) => { + if (!(data instanceof Uint8Array)) data = new Uint8Array(data); + this.buffer = this.appendArray(this.buffer, data); + if (this.tracing) + this.trace(`Received ${data.length} bytes: ${this.hexConvert(data)}`); + }); + } + + getInfo() { + const VID = this.serial.getVID(); + const PID = this.serial.getPID(); + return `WebDevice VendorID 0x${VID.toString(16)} ProductID 0x${PID.toString(16)}`; + } + + getPid() { + return this.serial.getPID(); + } + + trace(message) { + const delta = Date.now() - this.lastTraceTime; + const prefix = `TRACE +${delta}ms`; + const traceMessage = `${prefix} ${message}`; + this.lastTraceTime = Date.now(); + console.log(traceMessage); + this.traceLog += traceMessage + "\n"; + } + + async returnTrace() { + try { + await navigator.clipboard.writeText(this.traceLog); + console.log("Trace log copied to clipboard!"); + } catch (err) { + console.error("Failed to copy trace log:", err); + } + } + + hexify(s) { + return Array.from(s) + .map((byte) => byte.toString(16).padStart(2, "0")) + .join(" ") + .padEnd(16, " "); + } + + hexConvert(uint8Array, autoSplit = true) { + if (autoSplit && uint8Array.length > 16) { + let result = ""; + let s = uint8Array; + while (s.length > 0) { + const line = s.slice(0, 16); + const asciiLine = String.fromCharCode(...line) + .split("") + .map((c) => + c === " " || (c >= " " && c <= "~" && c !== " ") ? c : "." + ) + .join(""); + s = s.slice(16); + result += `\n ${this.hexify(line.slice(0, 8))} ${this.hexify( + line.slice(8) + )} | ${asciiLine}`; + } + return result; + } else { + return this.hexify(uint8Array); + } + } + + appendArray(arr1, arr2) { + const combined = new Uint8Array(arr1.length + arr2.length); + combined.set(arr1); + combined.set(arr2, arr1.length); + return combined; + } + + async sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + + async connect(baud = 115200) { + await this.serial.open(baud); + this.baudrate = baud; + if (this.tracing) this.trace(`Serial opened at ${baud} baud`); + } + + async disconnect() { + await this.serial.close(); + if (this.tracing) this.trace("Serial connection closed"); + } + + async setRTS(state) { + await this.serial.setRTS(state); + if (this.tracing) this.trace(`Set RTS = ${state}`); + } + + async setDTR(state) { + await this.serial.setDTR(state); + if (this.tracing) this.trace(`Set DTR = ${state}`); + } + + inWaiting() { + return this.buffer.length; + } + + async flushInput() { + this.buffer = new Uint8Array(0); + } + + slipWriter(data) { + const outData = []; + outData.push(0xc0); + for (let i = 0; i < data.length; i++) { + if (data[i] === 0xdb) outData.push(0xdb, 0xdd); + else if (data[i] === 0xc0) outData.push(0xdb, 0xdc); + else outData.push(data[i]); + } + outData.push(0xc0); + return new Uint8Array(outData); + } + + async write(data) { + const outData = this.slipWriter(data); + if (this.tracing) { + this.trace(`Write ${outData.length} bytes: ${this.hexConvert(outData)}`); + } + await this.serial.sendBuffer(outData); + } + + async newRead(numBytes, timeout) { + const start = Date.now(); + while (this.buffer.length < numBytes && Date.now() - start < timeout) { + if (!this.serial.isOpened()) { + return null; + } + await this.sleep(10); + } + const out = this.buffer.slice(0, numBytes); + this.buffer = this.buffer.slice(numBytes); + return out; + } + + async *read(timeout = 1000) { + let partialPacket = null; + let isEscaping = false; + const SLIP_END = 0xc0; + const SLIP_ESC = 0xdb; + const SLIP_ESC_END = 0xdc; + const SLIP_ESC_ESC = 0xdd; + + while (true) { + const data = await this.newRead(1, timeout); + if (!data || data.length === 0) break; + + const byte = data[0]; + if (partialPacket === null) { + if (byte === SLIP_END) partialPacket = new Uint8Array(0); + } else if (isEscaping) { + isEscaping = false; + if (byte === SLIP_ESC_END) + partialPacket = this.appendArray(partialPacket, new Uint8Array([SLIP_END])); + else if (byte === SLIP_ESC_ESC) + partialPacket = this.appendArray(partialPacket, new Uint8Array([SLIP_ESC])); + else throw new Error(`Invalid SLIP escape: 0x${byte.toString(16)}`); + } else if (byte === SLIP_ESC) { + isEscaping = true; + } else if (byte === SLIP_END) { + if (partialPacket.length > 0) { + if (this.tracing) + this.trace(`Yield packet: ${this.hexConvert(partialPacket)}`); + yield partialPacket; + partialPacket = null; + } + } else { + partialPacket = this.appendArray(partialPacket, new Uint8Array([byte])); + } + } + } + + detectPanicHandler(input) { + const guruMeditationRegex = + /G?uru Meditation Error: (?:Core \d panic'ed \(([a-zA-Z ]*)\))?/; + const fatalExceptionRegex = + /F?atal exception \(\d+\): (?:([a-zA-Z ]*)?.*epc)?/; + + const inputString = new TextDecoder("utf-8").decode(input); + const match = + inputString.match(guruMeditationRegex) || + inputString.match(fatalExceptionRegex); + + if (match) { + const cause = match[1] || match[2]; + const msg = `Guru Meditation Error detected${cause ? ` (${cause})` : ""}`; + throw new Error(msg); + } + } +} + + +Web.SerialTransport = Transport; + +}); \ No newline at end of file diff --git a/common/modules/mixly-modules/web/serialport.js b/common/modules/mixly-modules/web/serialport.js index a264ed39..2b72d2b8 100644 --- a/common/modules/mixly-modules/web/serialport.js +++ b/common/modules/mixly-modules/web/serialport.js @@ -108,6 +108,7 @@ class WebSerialPort extends Serial { #stringTemp_ = ''; constructor(port) { super(port); + this.#serialport_ = WebSerialPort.getPort(port); } #addEventsListener_() { @@ -230,6 +231,16 @@ class WebSerialPort extends Serial { return this.setDTRAndRTS(this.getDTR(), rts); } + getVID() { + const info = this.#serialport_.getInfo(); + return info.usbVendorId; + } + + getPID() { + const info = this.#serialport_.getInfo(); + return info.usbProductId; + } + onBuffer(buffer) { super.onBuffer(buffer); for (let i = 0; i < buffer.length; i++) { diff --git a/common/modules/mixly-modules/web/usb-mini.js b/common/modules/mixly-modules/web/usb-mini.js index 6ce5ec7b..44daa549 100644 --- a/common/modules/mixly-modules/web/usb-mini.js +++ b/common/modules/mixly-modules/web/usb-mini.js @@ -121,6 +121,7 @@ class USBMini extends Serial { #dataLength_ = 64; constructor(port) { super(port); + this.#device_ = USBMini.getPort(port); } #addEventsListener_() { @@ -315,6 +316,14 @@ class USBMini extends Serial { return this.setDTRAndRTS(this.getDTR(), rts); } + getVID() { + return this.#device_.vendorId; + } + + getPID() { + return this.#device_.productId; + } + onBuffer(buffer) { super.onBuffer(buffer); for (let i = 0; i < buffer.length; i++) { diff --git a/common/modules/mixly-modules/web/usb.js b/common/modules/mixly-modules/web/usb.js index dde514d9..14c489ba 100644 --- a/common/modules/mixly-modules/web/usb.js +++ b/common/modules/mixly-modules/web/usb.js @@ -117,6 +117,7 @@ class USB extends Serial { #stringTemp_ = ''; constructor(port) { super(port); + this.#device_ = USB.getPort(port); } #addEventsListener_() { @@ -223,6 +224,14 @@ class USB extends Serial { return this.setDTRAndRTS(this.getDTR(), rts); } + getVID() { + return this.#device_.vendorId; + } + + getPID() { + return this.#device_.productId; + } + onBuffer(buffer) { super.onBuffer(buffer); for (let i = 0; i < buffer.length; i++) {