From 53640b6e250d5cdbed11f5c5e56858504e5da7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E7=AB=8B=E5=B8=AE?= <3294713004@qq.com> Date: Sun, 15 Dec 2024 01:52:05 +0800 Subject: [PATCH] =?UTF-8?q?Update:=20=E5=9C=A8=E7=BA=BF=E7=89=88=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E5=AF=B9HID=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/modules/mixly-modules/common/app.js | 3 + common/modules/mixly-modules/deps.json | 29 +- common/modules/mixly-modules/web/ampy.js | 21 +- .../modules/mixly-modules/web/burn-upload.js | 24 +- common/modules/mixly-modules/web/hid.js | 236 +++++++++ common/modules/mixly-modules/web/serial.js | 277 ++-------- .../modules/mixly-modules/web/serialport.js | 498 +++++++++--------- common/modules/mixly-modules/web/usb.js | 27 +- 8 files changed, 577 insertions(+), 538 deletions(-) create mode 100644 common/modules/mixly-modules/web/hid.js diff --git a/common/modules/mixly-modules/common/app.js b/common/modules/mixly-modules/common/app.js index e0d3690d..cf885720 100644 --- a/common/modules/mixly-modules/common/app.js +++ b/common/modules/mixly-modules/common/app.js @@ -239,6 +239,9 @@ class App extends Component { id: 'command-burn-btn', displayText: Msg.Lang['nav.btn.burn'], preconditionFn: () => { + if (!goog.isElectron && BOARD.web.com === 'hid') { + return false; + } return SELECTED_BOARD?.nav?.burn; }, callback: () => BU.initBurn(), diff --git a/common/modules/mixly-modules/deps.json b/common/modules/mixly-modules/deps.json index 5f947408..eccf9896 100644 --- a/common/modules/mixly-modules/deps.json +++ b/common/modules/mixly-modules/deps.json @@ -1573,7 +1573,6 @@ "Mixly.HTMLTemplate", "Mixly.MString", "Mixly.Web.Serial", - "Mixly.Web.USB", "Mixly.Web.Ampy" ], "provide": [ @@ -1629,6 +1628,17 @@ "Mixly.Web.FS" ] }, + { + "path": "/web/hid.js", + "require": [ + "Mixly.Serial", + "Mixly.Registry", + "Mixly.Web" + ], + "provide": [ + "Mixly.Web.HID" + ] + }, { "path": "/web/lms.js", "require": [ @@ -1648,13 +1658,11 @@ { "path": "/web/serial.js", "require": [ - "Mixly.Serial", + "Mixly.Config", "Mixly.Env", - "Mixly.Nav", - "Mixly.Msg", - "Mixly.Debug", - "Mixly.Registry", - "Mixly.Web" + "Mixly.Web.SerialPort", + "Mixly.Web.USB", + "Mixly.Web.HID" ], "provide": [ "Mixly.Web.Serial" @@ -1663,7 +1671,8 @@ { "path": "/web/serialport.js", "require": [ - "Mixly.MString", + "Mixly.Serial", + "Mixly.Registry", "Mixly.Web" ], "provide": [ @@ -1675,10 +1684,6 @@ "require": [ "DAPjs", "Mixly.Serial", - "Mixly.Env", - "Mixly.Nav", - "Mixly.Msg", - "Mixly.Debug", "Mixly.Registry", "Mixly.Web" ], diff --git a/common/modules/mixly-modules/web/ampy.js b/common/modules/mixly-modules/web/ampy.js index 93c06602..54e8cd93 100644 --- a/common/modules/mixly-modules/web/ampy.js +++ b/common/modules/mixly-modules/web/ampy.js @@ -33,11 +33,14 @@ class AmpyExt extends Ampy { #device_ = null; #receiveTemp_ = []; - #writeBuffer_ = false; + #writeBuffer_ = true; #active_ = false; - constructor(device) { + #dataLength_ = 256; + constructor(device, writeBuffer = true, dataLength = 256) { super(); this.#device_ = device; + this.#writeBuffer_ = writeBuffer; + this.#dataLength_ = dataLength; this.#addEventsListener_(); } @@ -134,16 +137,20 @@ class AmpyExt extends Ampy { async exec(str) { if (this.#writeBuffer_) { const buffer = this.#device_.encode(str); - const len = Math.ceil(buffer.length / 256); + const len = Math.ceil(buffer.length / this.#dataLength_); for (let i = 0; i < len; i++) { - const writeBuffer = buffer.slice(i * 256, Math.min((i + 1) * 256, buffer.length)); + const start = i * this.#dataLength_; + const end = Math.min((i + 1) * this.#dataLength_, buffer.length); + const writeBuffer = buffer.slice(start, end); await this.#device_.sendBuffer(writeBuffer); await this.#device_.sleep(10); } } else { - for (let i = 0; i < str.length / 60; i++) { - let newData = str.substring(i * 60, (i + 1) * 60); - await this.#device_.sendString(newData); + for (let i = 0; i < str.length / this.#dataLength_; i++) { + const start = i * this.#dataLength_; + const end = Math.min((i + 1) * this.#dataLength_, str.length); + let data = str.substring(start, end); + await this.#device_.sendString(data); await this.#device_.sleep(10); } } diff --git a/common/modules/mixly-modules/web/burn-upload.js b/common/modules/mixly-modules/web/burn-upload.js index a60d01ed..ace3852c 100644 --- a/common/modules/mixly-modules/web/burn-upload.js +++ b/common/modules/mixly-modules/web/burn-upload.js @@ -16,7 +16,6 @@ goog.require('Mixly.Debug'); goog.require('Mixly.HTMLTemplate'); goog.require('Mixly.MString'); goog.require('Mixly.Web.Serial'); -goog.require('Mixly.Web.USB'); goog.require('Mixly.Web.Ampy'); goog.provide('Mixly.Web.BU'); @@ -37,7 +36,6 @@ const { const { Serial, BU, - USB, Ampy } = Web; @@ -61,11 +59,7 @@ BU.FILMWARE_LAYER = new HTMLTemplate( const BAUD = goog.platform() === 'darwin' ? 460800 : 921600; BU.requestPort = async () => { - if (SELECTED_BOARD.web.com === 'usb') { - await USB.requestPort(); - } else { - await Serial.requestPort(); - } + await Serial.requestPort(); } const readBinFile = (path, offset) => { @@ -182,7 +176,7 @@ BU.burnByUSB = () => { $(".layui-layer-page").css("z-index","198910151"); $("#mixly-loader-btn").hide(); let prevPercent = 0; - USB.DAPLink.on(DAPjs.DAPLink.EVENT_PROGRESS, progress => { + Serial.DAPLink.on(DAPjs.DAPLink.EVENT_PROGRESS, progress => { const nowPercent = Math.floor(progress * 100); if (nowPercent > prevPercent) { prevPercent = nowPercent; @@ -194,7 +188,7 @@ BU.burnByUSB = () => { const rightStr = (new Array(50 - nowProgressLen).fill('-')).join(''); statusBarTerminal.addValue(`[${leftStr}${rightStr}] ${nowPercent}%\n`); }); - USB.flash(buffer) + Serial.flash(buffer) .then(() => { layer.close(index); layer.msg(Msg.Lang['shell.burnSucc'], { time: 1000 }); @@ -212,7 +206,7 @@ BU.burnByUSB = () => { toolConfig.baudRates = prevBaud; await serialport.setBaudRate(prevBaud); } - USB.DAPLink.removeAllListeners(DAPjs.DAPLink.EVENT_PROGRESS); + Serial.DAPLink.removeAllListeners(DAPjs.DAPLink.EVENT_PROGRESS); }); }, end: function () { @@ -545,6 +539,14 @@ BU.uploadWithAmpy = (portName) => { mainStatusBarTabs.changeTo('output'); const mainWorkspace = Workspace.getMain(); const editor = mainWorkspace.getEditorsManager().getActive(); + let useBuffer = true, dataLength = 256; + if (BOARD.web.com === 'usb') { + useBuffer = false; + dataLength = 64; + } else if (BOARD.web.com === 'hid') { + useBuffer = true; + dataLength = 30; + } const layerNum = layer.open({ type: 1, title: Msg.Lang['shell.uploading'] + '...', @@ -554,7 +556,7 @@ BU.uploadWithAmpy = (portName) => { closeBtn: 0, success: async function (layero, index) { const serial = new Serial(portName); - const ampy = new Ampy(serial); + const ampy = new Ampy(serial, useBuffer, dataLength); const code = editor.getCode(); let closePromise = Promise.resolve(); if (statusBarSerial) { diff --git a/common/modules/mixly-modules/web/hid.js b/common/modules/mixly-modules/web/hid.js new file mode 100644 index 00000000..3a9ddd2f --- /dev/null +++ b/common/modules/mixly-modules/web/hid.js @@ -0,0 +1,236 @@ +goog.loadJs('web', () => { + +goog.require('Mixly.Serial'); +goog.require('Mixly.Registry'); +goog.require('Mixly.Web'); +goog.provide('Mixly.Web.HID'); + +const { + Serial, + Registry, + Web +} = Mixly; + + +class WebHID extends Serial { + static { + this.portToNameRegistry = new Registry(); + this.nameToPortRegistry = new Registry(); + + this.getConfig = function () { + return Serial.getConfig(); + } + + this.getSelectedPortName = function () { + return Serial.getSelectedPortName(); + } + + this.getCurrentPortsName = function () { + return Serial.getCurrentPortsName(); + } + + this.refreshPorts = function () { + let portsName = []; + for (let name of this.nameToPortRegistry.keys()) { + portsName.push({ name }); + } + Serial.renderSelectBox(portsName); + } + + this.requestPort = async function () { + const devices = await navigator.hid.requestDevice({ + filters: [] + }); + this.addPort(devices[0]); + this.refreshPorts(); + } + + this.getPort = function (name) { + return this.nameToPortRegistry.getItem(name); + } + + this.addPort = function (device) { + if (this.portToNameRegistry.hasKey(device)) { + return; + } + let name = ''; + for (let i = 1; i <= 20; i++) { + name = `hid${i}`; + if (this.nameToPortRegistry.hasKey(name)) { + continue; + } + break; + } + this.portToNameRegistry.register(device, name); + this.nameToPortRegistry.register(name, device); + } + + this.removePort = function (device) { + if (!this.portToNameRegistry.hasKey(device)) { + return; + } + const name = this.portToNameRegistry.getItem(device); + if (!name) { + return; + } + this.portToNameRegistry.unregister(device); + this.nameToPortRegistry.unregister(name); + } + + this.addEventsListener = function () { + navigator?.hid?.addEventListener('connect', (event) => { + this.addPort(event.device); + this.refreshPorts(); + }); + + navigator?.hid?.addEventListener('disconnect', (event) => { + event.device.onclose && event.device.onclose(); + this.removePort(event.device); + this.refreshPorts(); + }); + } + + this.init = function () { + navigator?.hid?.getDevices().then((devices) => { + for (let device of devices) { + this.addPort(device); + } + }); + this.addEventsListener(); + } + } + + #device_ = null; + #keepReading_ = null; + #reader_ = null; + #writer_ = null; + #stringTemp_ = ''; + constructor(port) { + super(port); + } + + #addEventsListener_() { + this.#addReadEventListener_(); + } + + async #addReadEventListener_() { + this.#device_.oninputreport = (event) => { + const { data, reportId } = 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.onBuffer(buffer); + }; + + this.#device_.onclose = () => { + if (!this.isOpened()) { + return; + } + super.close(); + this.#stringTemp_ = ''; + this.onClose(1); + } + } + + async open(baud) { + return new Promise((resolve, reject) => { + const portsName = Serial.getCurrentPortsName(); + const currentPortName = this.getPortName(); + if (!portsName.includes(currentPortName)) { + reject('无可用设备'); + return; + } + if (this.isOpened()) { + resolve(); + return; + } + baud = baud ?? this.getBaudRate(); + this.#device_ = WebHID.getPort(currentPortName); + this.#device_.open() + .then(() => { + super.open(baud); + super.setBaudRate(baud); + this.onOpen(); + this.#addEventsListener_(); + resolve(); + }) + .catch(reject); + }); + } + + async close() { + if (!this.isOpened()) { + return; + } + super.close(); + await this.#device_.close(); + this.#stringTemp_ = ''; + this.#device_.oninputreport = null; + this.#device_.onclose = null; + this.onClose(1); + } + + async setBaudRate(baud) { + return Promise.resolve(); + } + + async sendString(str) { + const buffer = this.encode(str); + return this.sendBuffer(buffer); + } + + async sendBuffer(buffer) { + return new Promise((resolve, reject) => { + 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); + } + this.#device_.sendReport(0, buffer) + .then(resolve) + .catch(reject); + }); + } + + async setDTRAndRTS(dtr, rts) { + return Promise.resolve(); + } + + async setDTR(dtr) { + return this.setDTRAndRTS(dtr, this.getRTS()); + } + + async setRTS(rts) { + return this.setDTRAndRTS(this.getDTR(), rts); + } + + onBuffer(buffer) { + super.onBuffer(buffer); + for (let i = 0; i < buffer.length; i++) { + super.onByte(buffer[i]); + } + const string = this.decodeBuffer(buffer); + if (!string) { + return; + } + for (let char of string) { + super.onChar(char); + if (['\r', '\n'].includes(char)) { + super.onString(this.#stringTemp_); + this.#stringTemp_ = ''; + } else { + this.#stringTemp_ += char; + } + } + } +} + +Web.HID = WebHID; + +}); \ No newline at end of file diff --git a/common/modules/mixly-modules/web/serial.js b/common/modules/mixly-modules/web/serial.js index aae81b64..ac9d4791 100644 --- a/common/modules/mixly-modules/web/serial.js +++ b/common/modules/mixly-modules/web/serial.js @@ -1,283 +1,84 @@ goog.loadJs('web', () => { -goog.require('Mixly.Serial'); +goog.require('Mixly.Config'); goog.require('Mixly.Env'); -goog.require('Mixly.Nav'); -goog.require('Mixly.Msg'); -goog.require('Mixly.Debug'); -goog.require('Mixly.Registry'); -goog.require('Mixly.Web'); +goog.require('Mixly.Web.SerialPort'); +goog.require('Mixly.Web.USB'); +goog.require('Mixly.Web.HID'); goog.provide('Mixly.Web.Serial'); +const { Config, Env, Web } = Mixly; + const { - Serial, - Env, - Nav, - Msg, - Debug, - Registry, - Web -} = Mixly; + SerialPort, + USB, + HID +} = Web; + +const { BOARD } = Config; + +let Device = SerialPort; + +if (BOARD.web.com === 'usb') { + Device = USB; +} else if (BOARD.web.com === 'hid') { + Device = HID; +} -class WebSerial extends Serial { +class WebSerial extends Device { static { - this.portToNameRegistry = new Registry(); - this.nameToPortRegistry = new Registry(); - this.getConfig = function () { - return Serial.getConfig(); + return Device.getConfig(); } this.getSelectedPortName = function () { - return Serial.getSelectedPortName(); + return Device.getSelectedPortName(); } this.getCurrentPortsName = function () { - return Serial.getCurrentPortsName(); + return Device.getCurrentPortsName(); } this.refreshPorts = function () { - let portsName = []; - for (let name of this.nameToPortRegistry.keys()) { - portsName.push({ name }); - } - Serial.renderSelectBox(portsName); + return Device.refreshPorts(); } this.requestPort = async function () { - const serialport = await navigator.serial.requestPort(); - this.addPort(serialport); - this.refreshPorts(); + return Device.requestPort(); } this.getPort = function (name) { - return this.nameToPortRegistry.getItem(name); + return Device.getPort(name); } - this.addPort = function (serialport) { - if (this.portToNameRegistry.hasKey(serialport)) { - return; - } - let name = ''; - for (let i = 1; i <= 20; i++) { - name = `serial${i}`; - if (this.nameToPortRegistry.hasKey(name)) { - continue; - } - break; - } - this.portToNameRegistry.register(serialport, name); - this.nameToPortRegistry.register(name, serialport); + this.addPort = function (device) { + return Device.addPort(device); } - this.removePort = function (serialport) { - if (!this.portToNameRegistry.hasKey(serialport)) { - return; - } - const name = this.portToNameRegistry.getItem(serialport); - if (!name) { - return; - } - this.portToNameRegistry.unregister(serialport); - this.nameToPortRegistry.unregister(name); + this.removePort = function (device) { + return Device.removePort(device); } this.addEventsListener = function () { - navigator?.serial?.addEventListener('connect', (event) => { - this.addPort(event.target); - this.refreshPorts(); - }); - - navigator?.serial?.addEventListener('disconnect', (event) => { - this.removePort(event.target); - this.refreshPorts(); - }); + return Device.addEventsListener(); } - if (!Env.hasSocketServer) { - navigator?.serial?.getPorts().then((serialports) => { - for (let serialport of serialports) { - this.addPort(serialport); - } - }); - this.addEventsListener(); + this.init = function () { + if (!Env.hasSocketServer) { + Device.init(); + } } + + this.init(); } - #serialport_ = null; - #keepReading_ = null; - #reader_ = null; - #writer_ = null; - #stringTemp_ = ''; constructor(port) { super(port); } - - #addEventsListener_() { - this.#addReadEventListener_(); - } - - async #addReadEventListener_() { - const { readable } = this.#serialport_; - while (readable && this.#keepReading_) { - this.#reader_ = readable.getReader(); - try { - while (true) { - const { value, done } = await this.#reader_.read(); - value && this.onBuffer(value); - if (done) { - break; - } - } - } catch (error) { - this.#keepReading_ = false; - Debug.error(error); - } finally { - this.#reader_ && this.#reader_.releaseLock(); - await this.close(); - } - } - } - - async open(baud) { - return new Promise((resolve, reject) => { - const portsName = Serial.getCurrentPortsName(); - const currentPortName = this.getPortName(); - if (!portsName.includes(currentPortName)) { - reject('无可用串口'); - return; - } - if (this.isOpened()) { - resolve(); - return; - } - baud = baud ?? this.getBaudRate(); - this.#serialport_ = WebSerial.getPort(currentPortName); - this.#serialport_.open({ baudRate: baud }) - .then(() => { - super.open(baud); - super.setBaudRate(baud); - this.#keepReading_ = true; - this.onOpen(); - this.#addEventsListener_(); - resolve(); - }) - .catch(reject); - }); - } - - async #waitForUnlock_(timeout) { - while ( - (this.#serialport_.readable && this.#serialport_.readable.locked) || - (this.#serialport_.writable && this.#serialport_.writable.locked) - ) { - await this.sleep(timeout); - } - } - - async close() { - if (!this.isOpened()) { - return; - } - super.close(); - if (this.#serialport_.readable?.locked) { - this.#keepReading_ = false; - await this.#reader_?.cancel(); - } - await this.#waitForUnlock_(400); - this.#reader_ = undefined; - await this.#serialport_.close(); - this.#stringTemp_ = ''; - this.onClose(1); - } - - async setBaudRate(baud) { - return new Promise((resolve, reject) => { - if (!this.isOpened() - || this.getBaudRate() === baud - || !this.baudRateIsLegal(baud)) { - resolve(); - return; - } - this.close() - .then(() => this.open(baud)) - .then(resolve) - .catch(reject); - }); - } - - async sendString(str) { - const buffer = this.encode(str); - return this.sendBuffer(buffer); - } - - async sendBuffer(buffer) { - return new Promise((resolve, reject) => { - const { writable } = this.#serialport_; - const writer = writable.getWriter(); - if (!(buffer instanceof Uint8Array)) { - buffer = new Uint8Array(buffer); - } - writer.write(buffer) - .then(() => { - writer.releaseLock(); - resolve(); - }) - .catch(() => { - writer.releaseLock(); - reject(); - }); - }); - } - - async setDTRAndRTS(dtr, rts) { - return new Promise((resolve, reject) => { - if (!this.isOpened()) { - resolve(); - return; - } - this.#serialport_.setSignals({ - dataTerminalReady: dtr, - requestToSend: rts - }) - .then(() => { - super.setDTRAndRTS(dtr, rts); - resolve(); - }) - .catch(reject); - }); - } - - async setDTR(dtr) { - return this.setDTRAndRTS(dtr, this.getRTS()); - } - - async setRTS(rts) { - return this.setDTRAndRTS(this.getDTR(), rts); - } - - onBuffer(buffer) { - super.onBuffer(buffer); - for (let i = 0; i < buffer.length; i++) { - super.onByte(buffer[i]); - } - const string = this.decodeBuffer(buffer); - if (!string) { - return; - } - for (let char of string) { - super.onChar(char); - if (['\r', '\n'].includes(char)) { - super.onString(this.#stringTemp_); - this.#stringTemp_ = ''; - } else { - this.#stringTemp_ += char; - } - } - } } + Web.Serial = WebSerial; }); \ 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 597ce61e..0b571ebf 100644 --- a/common/modules/mixly-modules/web/serialport.js +++ b/common/modules/mixly-modules/web/serialport.js @@ -1,285 +1,275 @@ goog.loadJs('web', () => { -goog.require('Mixly.MString'); +goog.require('Mixly.Serial'); +goog.require('Mixly.Registry'); goog.require('Mixly.Web'); goog.provide('Mixly.Web.SerialPort'); -const { MString, Web } = Mixly; +const { + Serial, + Registry, + Web +} = Mixly; -const { SerialPort } = Web; -SerialPort.output = []; -SerialPort.inputBuffer = []; -SerialPort.outputBuffer = []; -SerialPort.refreshInputBuffer = false; -SerialPort.refreshOutputBuffer = true; -SerialPort.obj = null; -SerialPort.onDataLine = null; -SerialPort.keepReading = false; +class WebSerialPort extends Serial { + static { + this.portToNameRegistry = new Registry(); + this.nameToPortRegistry = new Registry(); -SerialPort.encoder = new TextEncoder('utf8'); -SerialPort.decoder = new TextDecoder('utf8'); -SerialPort.dtr = false; -SerialPort.rts = false; -SerialPort.name = 'serialport'; - -SerialPort.connect = (baud = 115200, onDataLine = (message) => {}) => { - return new Promise((resolve, reject) => { - if (SerialPort.isConnected()) { - resolve(); - return; + this.getConfig = function () { + return Serial.getConfig(); } - navigator.serial.requestPort() - .then((device) => { - SerialPort.obj = device; - return device.open({ baudRate: baud }); - }) - .then(() => { - SerialPort.keepReading = true; - SerialPort.onDataLine = onDataLine; - SerialPort.addReadEvent(onDataLine); - resolve(); - }) - .catch((error) => { - SerialPort.obj = null; - reject(error); - }); - }); -} -SerialPort.close = async () => { - if (SerialPort.isConnected()) { - SerialPort.keepReading = false; - if (!SerialPort.isConnected()) { - return; + this.getSelectedPortName = function () { + return Serial.getSelectedPortName(); } - const serialObj = SerialPort.obj; - if (serialObj.readable && serialObj.readable.locked) { - try { - await SerialPort.reader.cancel(); - SerialPort.reader.releaseLock(); - } catch (error) { - console.log(error); + + this.getCurrentPortsName = function () { + return Serial.getCurrentPortsName(); + } + + this.refreshPorts = function () { + let portsName = []; + for (let name of this.nameToPortRegistry.keys()) { + portsName.push({ name }); } + Serial.renderSelectBox(portsName); } - if (serialObj.writable && serialObj.writable.locked) { - try { - SerialPort.writer.releaseLock(); - } catch (error) { - console.log(error); + + this.requestPort = async function () { + const serialport = await navigator.serial.requestPort(); + this.addPort(serialport); + this.refreshPorts(); + } + + this.getPort = function (name) { + return this.nameToPortRegistry.getItem(name); + } + + this.addPort = function (serialport) { + if (this.portToNameRegistry.hasKey(serialport)) { + return; } - } - try { - await serialObj.close(); - } catch (error) { - console.log(error); - } - SerialPort.obj = null; - } -} - -SerialPort.isConnected = () => { - return SerialPort.obj ? true : false; -} - -SerialPort.readLine = () => { - var text = "", ch = ''; - var endWithLF = false; - let i = 0; - do { - ch = SerialPort.readChar(); - if (ch.length) { - if (ch === '\n') { - endWithLF = true; - } else { - text += ch; - } - } - } while (ch.length && !endWithLF) - return { text: text, endWithLF: endWithLF }; -} - -SerialPort.readChar = () => { - var readBuf = []; - var buffLength = 0; - var text = ""; - const len = SerialPort.outputBuffer.length; - /* UTF-8编码方式 - * ------------------------------------------------------------ - * |1字节 0xxxxxxx | - * |2字节 110xxxxx 10xxxxxx | - * |3字节 1110xxxx 10xxxxxx 10xxxxxx | - * |4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | - * |5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx | - * |6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx| - * ------------------------------------------------------------ - */ - for (var i = 0; i < len; i++) { - const data = SerialPort.outputBuffer.shift(); - if ((data & 0x80) == 0x00) { - text = String.fromCharCode(data); - break; - } else if ((data & 0xc0) == 0x80) { - readBuf.push(data); - if (readBuf.length >= buffLength) { - text = SerialPort.decoder.decode(new Uint8Array(readBuf)); + let name = ''; + for (let i = 1; i <= 20; i++) { + name = `serial${i}`; + if (this.nameToPortRegistry.hasKey(name)) { + continue; + } break; } - } else { - let dataNum = data & 0xe0; - switch (dataNum) { - case 0xfc: - buffLength = 6; - break; - case 0xf8: - buffLength = 5; - break; - case 0xf0: - buffLength = 4; - break; - case 0xe0: - buffLength = 3; - break; - case 0xc0: - default: - buffLength = 2; + this.portToNameRegistry.register(serialport, name); + this.nameToPortRegistry.register(name, serialport); + } + + this.removePort = function (serialport) { + if (!this.portToNameRegistry.hasKey(serialport)) { + return; } - readBuf.push(data); + const name = this.portToNameRegistry.getItem(serialport); + if (!name) { + return; + } + this.portToNameRegistry.unregister(serialport); + this.nameToPortRegistry.unregister(name); + } + + this.addEventsListener = function () { + navigator?.serial?.addEventListener('connect', (event) => { + this.addPort(event.target); + this.refreshPorts(); + }); + + navigator?.serial?.addEventListener('disconnect', (event) => { + this.removePort(event.target); + this.refreshPorts(); + }); + } + + this.init = function () { + navigator?.serial?.getPorts().then((serialports) => { + for (let serialport of serialports) { + this.addPort(serialport); + } + }); + this.addEventsListener(); } } - return text; -} -SerialPort.startReadLine = (onDataLine = (message) => {}) => { - SerialPort.readLineTimer = window.setTimeout(() => { - if (!SerialPort.keepReading) { - window.clearTimeout(SerialPort.readLineTimer); + #serialport_ = null; + #keepReading_ = null; + #reader_ = null; + #writer_ = null; + #stringTemp_ = ''; + constructor(port) { + super(port); + } + + #addEventsListener_() { + this.#addReadEventListener_(); + } + + async #addReadEventListener_() { + const { readable } = this.#serialport_; + while (readable && this.#keepReading_) { + this.#reader_ = readable.getReader(); + try { + while (true) { + const { value, done } = await this.#reader_.read(); + value && this.onBuffer(value); + if (done) { + break; + } + } + } catch (error) { + this.#keepReading_ = false; + Debug.error(error); + } finally { + this.#reader_ && this.#reader_.releaseLock(); + await this.close(); + } + } + } + + async open(baud) { + return new Promise((resolve, reject) => { + const portsName = Serial.getCurrentPortsName(); + const currentPortName = this.getPortName(); + if (!portsName.includes(currentPortName)) { + reject('无可用串口'); + return; + } + if (this.isOpened()) { + resolve(); + return; + } + baud = baud ?? this.getBaudRate(); + this.#serialport_ = WebSerialPort.getPort(currentPortName); + this.#serialport_.open({ baudRate: baud }) + .then(() => { + super.open(baud); + super.setBaudRate(baud); + this.#keepReading_ = true; + this.onOpen(); + this.#addEventsListener_(); + resolve(); + }) + .catch(reject); + }); + } + + async #waitForUnlock_(timeout) { + while ( + (this.#serialport_.readable && this.#serialport_.readable.locked) || + (this.#serialport_.writable && this.#serialport_.writable.locked) + ) { + await this.sleep(timeout); + } + } + + async close() { + if (!this.isOpened()) { return; } - let endWithLF = false; - do { - const readObj = SerialPort.readLine(); - endWithLF = readObj.endWithLF; - const { text } = readObj; - SerialPort.output.push((SerialPort.output.length? SerialPort.output.pop() : '') + text); - if (endWithLF) { - const len = SerialPort.output.length; - SerialPort.output[len - 1] = MString.decode(SerialPort.output[len - 1]); - if (len) { - onDataLine(SerialPort.output[len - 1]); - } - SerialPort.output.push(''); - } - } while (endWithLF); - while (SerialPort.output.length > 500) { - SerialPort.output.shift(); + super.close(); + if (this.#serialport_.readable?.locked) { + this.#keepReading_ = false; + await this.#reader_?.cancel(); } - if (SerialPort.keepReading) { - SerialPort.startReadLine(onDataLine); - } - }, 100); -} + await this.#waitForUnlock_(400); + this.#reader_ = undefined; + await this.#serialport_.close(); + this.#stringTemp_ = ''; + this.onClose(1); + } -SerialPort.addReadEvent = async (onDataLine = (message) => {}) => { - SerialPort.output = []; - SerialPort.inputBuffer = []; - SerialPort.outputBuffer = []; - SerialPort.refreshInputBuffer = false; - SerialPort.refreshOutputBuffer = true; - SerialPort.startReadLine(onDataLine); - while (SerialPort.obj.readable && SerialPort.keepReading) { - SerialPort.reader = SerialPort.obj.readable.getReader(); - try { - while (true) { - const { value, done } = await SerialPort.reader.read(); - if (SerialPort.refreshOutputBuffer && value) { - SerialPort.outputBuffer = [ ...SerialPort.outputBuffer, ...value ]; - } - if (SerialPort.refreshInputBuffer && value) { - SerialPort.inputBuffer = [ ...SerialPort.inputBuffer, ...value ]; - } - if (done) { - break; - } + async setBaudRate(baud) { + return new Promise((resolve, reject) => { + if (!this.isOpened() + || this.getBaudRate() === baud + || !this.baudRateIsLegal(baud)) { + resolve(); + return; + } + this.close() + .then(() => this.open(baud)) + .then(resolve) + .catch(reject); + }); + } + + async sendString(str) { + const buffer = this.encode(str); + return this.sendBuffer(buffer); + } + + async sendBuffer(buffer) { + return new Promise((resolve, reject) => { + const { writable } = this.#serialport_; + const writer = writable.getWriter(); + if (!(buffer instanceof Uint8Array)) { + buffer = new Uint8Array(buffer); + } + writer.write(buffer) + .then(() => { + writer.releaseLock(); + resolve(); + }) + .catch(() => { + writer.releaseLock(); + reject(); + }); + }); + } + + async setDTRAndRTS(dtr, rts) { + return new Promise((resolve, reject) => { + if (!this.isOpened()) { + resolve(); + return; + } + this.#serialport_.setSignals({ + dataTerminalReady: dtr, + requestToSend: rts + }) + .then(() => { + super.setDTRAndRTS(dtr, rts); + resolve(); + }) + .catch(reject); + }); + } + + async setDTR(dtr) { + return this.setDTRAndRTS(dtr, this.getRTS()); + } + + async setRTS(rts) { + return this.setDTRAndRTS(this.getDTR(), rts); + } + + onBuffer(buffer) { + super.onBuffer(buffer); + for (let i = 0; i < buffer.length; i++) { + super.onByte(buffer[i]); + } + const string = this.decodeBuffer(buffer); + if (!string) { + return; + } + for (let char of string) { + super.onChar(char); + if (['\r', '\n'].includes(char)) { + super.onString(this.#stringTemp_); + this.#stringTemp_ = ''; + } else { + this.#stringTemp_ += char; } - } catch (error) { - console.log(error); - } finally { - SerialPort.reader.releaseLock(); } } } -SerialPort.AddOnConnectEvent = (onConnect) => { - navigator.serial.addEventListener('connect', (event) => { - onConnect(); - }); -} - -SerialPort.AddOnDisconnectEvent = (onDisconnect) => { - navigator.serial.addEventListener('disconnect', (event) => { - SerialPort.obj && SerialPort.close(); - onDisconnect(); - }); -} - -SerialPort.writeString = async (str) => { - const buffer = SerialPort.encoder.encode(str); - await SerialPort.writeByteArr(buffer); -} - -SerialPort.writeByteArr = async (buffer) => { - const writer = SerialPort.obj.writable.getWriter(); - await writer.write(new Int8Array(buffer).buffer); - writer.releaseLock(); - await SerialPort.sleep(200); -} - -SerialPort.writeCtrlA = async () => { - await SerialPort.writeByteArr([1, 13, 10]); -} - -SerialPort.writeCtrlB = async () => { - await SerialPort.writeByteArr([2, 13, 10]); -} - -SerialPort.writeCtrlC = async () => { - await SerialPort.writeByteArr([3, 13, 10]); -} - -SerialPort.writeCtrlD = async () => { - await SerialPort.writeByteArr([3, 4]); -} - -SerialPort.setBaudRate = async (baud) => { - SerialPort.keepReading = false; - const serialObj = SerialPort.obj; - await SerialPort.close(); - await serialObj.open({ baudRate: baud - 0 }); - SerialPort.obj = serialObj; - SerialPort.keepReading = true; - SerialPort.setSignals(SerialPort.dtr, SerialPort.rts); - SerialPort.addReadEvent(SerialPort.onDataLine); -} - -SerialPort.setDTR = async (value) => { - SerialPort.dtr = value; - await SerialPort.obj.setSignals({ dataTerminalReady: value }); -} - -SerialPort.setRTS = async (value) => { - SerialPort.rts = value; - await SerialPort.obj.setSignals({ requestToSend: value }); -} - -SerialPort.setSignals = async (dtr, rts) => { - SerialPort.dtr = dtr; - SerialPort.rts = rts; - await SerialPort.obj.setSignals({ dataTerminalReady: dtr, requestToSend: rts }); -} - -SerialPort.sleep = (ms) => { - return new Promise(resolve => setTimeout(resolve, ms)); -} +Web.SerialPort = WebSerialPort; }); \ No newline at end of file diff --git a/common/modules/mixly-modules/web/usb.js b/common/modules/mixly-modules/web/usb.js index 5fdd0f66..e93d5462 100644 --- a/common/modules/mixly-modules/web/usb.js +++ b/common/modules/mixly-modules/web/usb.js @@ -2,20 +2,12 @@ goog.loadJs('web', () => { goog.require('DAPjs'); goog.require('Mixly.Serial'); -goog.require('Mixly.Env'); -goog.require('Mixly.Nav'); -goog.require('Mixly.Msg'); -goog.require('Mixly.Debug'); goog.require('Mixly.Registry'); goog.require('Mixly.Web'); goog.provide('Mixly.Web.USB'); const { Serial, - Env, - Nav, - Msg, - Debug, Registry, Web } = Mixly; @@ -93,22 +85,25 @@ class USB extends Serial { } this.addEventsListener = function () { - navigator.usb.addEventListener('connect', (event) => { + navigator?.usb?.addEventListener('connect', (event) => { this.addPort(event.device); this.refreshPorts(); }); - navigator.usb.addEventListener('disconnect', (event) => { + navigator?.usb?.addEventListener('disconnect', (event) => { this.removePort(event.device); this.refreshPorts(); }); } - navigator.usb.getDevices().then((devices) => { - for (let device of devices) { - this.addPort(device); - } - }); - this.addEventsListener(); + + this.init = function () { + navigator?.usb?.getDevices().then((devices) => { + for (let device of devices) { + this.addPort(device); + } + }); + this.addEventsListener(); + } } #device_ = null;