From a67284afe55524c3396e8e8a4924076bfa282d18 Mon Sep 17 00:00:00 2001 From: yczpf2019 Date: Sat, 24 Jan 2026 21:04:10 +0800 Subject: [PATCH] =?UTF-8?q?feat(websocket-arduino):=20=E5=A7=94=E6=89=98?= =?UTF-8?q?=E7=BB=99=20WebCompiler.ArduShell=20=E5=A4=84=E7=90=86=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mixly-modules/web-socket/arduino-shell.js | 519 +++++++++--------- 1 file changed, 265 insertions(+), 254 deletions(-) diff --git a/mixly/common/modules/mixly-modules/web-socket/arduino-shell.js b/mixly/common/modules/mixly-modules/web-socket/arduino-shell.js index 84f71d16..2b58558c 100644 --- a/mixly/common/modules/mixly-modules/web-socket/arduino-shell.js +++ b/mixly/common/modules/mixly-modules/web-socket/arduino-shell.js @@ -1,275 +1,286 @@ goog.loadJs('web', () => { -goog.require('layui'); -goog.require('dayjs.duration'); -goog.require('Mixly.Boards'); -goog.require('Mixly.Debug'); -goog.require('Mixly.LayerExt'); -goog.require('Mixly.Msg'); -goog.require('Mixly.Workspace'); -goog.require('Mixly.LayerProgress'); -goog.require('Mixly.WebSocket.Serial'); -goog.provide('Mixly.WebSocket.ArduShell'); + goog.require('layui'); + goog.require('dayjs.duration'); + goog.require('Mixly.Boards'); + goog.require('Mixly.Debug'); + goog.require('Mixly.LayerExt'); + goog.require('Mixly.Msg'); + goog.require('Mixly.Workspace'); + goog.require('Mixly.LayerProgress'); + goog.require('Mixly.WebSocket.Serial'); + goog.require('Mixly.WebCompiler.ArduShell'); + goog.provide('Mixly.WebSocket.ArduShell'); -const { - Boards, - Debug, - LayerExt, - Msg, - Workspace, - LayerProgress, - WebSocket -} = Mixly; + const { + Boards, + Debug, + LayerExt, + Msg, + Workspace, + LayerProgress, + WebSocket, + WebCompiler = {} + } = Mixly; -const { Serial } = WebSocket; + // 动态获取 WebCompiler.ArduShell,用于本地上传 + const getWebCompilerArduShell = () => Mixly.WebCompiler?.ArduShell; -const { layer } = layui; + const { Serial } = WebSocket; + + const { layer } = layui; -class WebSocketArduShell { - static { - this.mixlySocket = null; - this.socket = null; - this.shell = null; + class WebSocketArduShell { + static { + this.mixlySocket = null; + this.socket = null; + this.shell = null; - this.getSocket = function () { - return this.socket; - } - - this.getMixlySocket = function () { - return this.mixlySocket; - } - - this.init = function (mixlySocket) { - this.mixlySocket = mixlySocket; - this.socket = mixlySocket.getSocket(); - this.shell = new WebSocketArduShell(); - const socket = this.socket; - - socket.on('arduino.dataEvent', (data) => { - if (data.length > 1000) { - return; - } - const { mainStatusBarTabs } = Mixly; - const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); - statusBarTerminal.addValue(data); - }); - - socket.on('arduino.errorEvent', (data) => { - const { mainStatusBarTabs } = Mixly; - const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); - try { - data = unescape(data.replace(/(_E[0-9A-F]{1}_[0-9A-F]{2}_[0-9A-F]{2})+/gm, '%$1')); - data = unescape(data.replace(/\\(u[0-9a-fA-F]{4})/gm, '%$1')); - } catch (error) { - Debug.error(error); - } - statusBarTerminal.addValue(data); - }); - } - - this.initCompile = function () { - if (!this.mixlySocket.isConnected()) { - layer.msg(Msg.Lang['websocket.offline'], { time: 1000 }); - return; + this.getSocket = function () { + return this.socket; } - const { mainStatusBarTabs } = Mixly; - const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); - mainStatusBarTabs.changeTo('output'); - mainStatusBarTabs.show(); - const mainWorkspace = Workspace.getMain(); - const editor = mainWorkspace.getEditorsManager().getActive(); - const code = editor.getCode(); - statusBarTerminal.setValue(`${Msg.Lang['shell.compiling']}...\n`); - this.shell.compile(code) - .then((info) => { - this.endCallback(info.code, info.time); - }) - .catch((error) => { - Debug.error(error); - statusBarTerminal.addValue(`\n==${Msg.Lang['shell.compileFailed']}==\n`); - }); - } - this.initUpload = function () { - if (!this.mixlySocket.isConnected()) { - layer.msg(Msg.Lang['websocket.offline'], { time: 1000 }); - return; + this.getMixlySocket = function () { + return this.mixlySocket; } - const port = Serial.getSelectedPortName(); - if (!port) { - layer.msg(Msg.Lang['statusbar.serial.noDevice'], { - time: 1000 - }); - return; - } - const { mainStatusBarTabs } = Mixly; - const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); - mainStatusBarTabs.changeTo('output'); - mainStatusBarTabs.show(); - statusBarTerminal.setValue(`${Msg.Lang['shell.uploading']}...\n`); - const mainWorkspace = Workspace.getMain(); - const editor = mainWorkspace.getEditorsManager().getActive(); - const code = editor.getCode(); - const statusBarSerial = mainStatusBarTabs.getStatusBarById(port); - const closePromise = statusBarSerial ? statusBarSerial.close() : Promise.resolve(); - closePromise - .then(() => { - return this.shell.upload(port, code) - }) - .then((info) => { - this.endCallback(info.code, info.time); - if (info.code || !Serial.portIsLegal(port)) { + + this.init = function (mixlySocket) { + this.mixlySocket = mixlySocket; + this.socket = mixlySocket.getSocket(); + this.shell = new WebSocketArduShell(); + const socket = this.socket; + + socket.on('arduino.dataEvent', (data) => { + if (data.length > 1000) { return; } - mainStatusBarTabs.add('serial', port); - mainStatusBarTabs.changeTo(port); - const statusBarSerial = mainStatusBarTabs.getStatusBarById(port); - statusBarSerial.open() - .then(() => { - const baudRates = code.match(/(?<=Serial.begin[\s]*\([\s]*)[0-9]*(?=[\s]*\))/g); - if (!baudRates?.length) { - return statusBarSerial.setBaudRate(9600); - } else { - return statusBarSerial.setBaudRate(baudRates[0] - 0); - } - }) - .catch(Debug.error); - }) - .catch((error) => { - Debug.error(error); - statusBarTerminal.addValue(`\n==${Msg.Lang['shell.uploadFailed']}==\n`); + const { mainStatusBarTabs } = Mixly; + const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); + statusBarTerminal.addValue(data); }); - } - this.endCallback = function (code, time) { - const { mainStatusBarTabs } = Mixly; - const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); - mainStatusBarTabs.changeTo('output'); - let message = ''; - if (code) { - message = (this.shell.isCompiling() ? Msg.Lang['shell.compileFailed'] : Msg.Lang['shell.uploadFailed']); - statusBarTerminal.addValue(`\n==${message}==\n`); - } else { - message = (this.shell.isCompiling() ? Msg.Lang['shell.compileSucc'] : Msg.Lang['shell.uploadSucc']); - statusBarTerminal.addValue(`\n==${message}(${Msg.Lang['shell.timeCost']} ${ - dayjs.duration(time).format('HH:mm:ss.SSS') - })==\n`); + socket.on('arduino.errorEvent', (data) => { + const { mainStatusBarTabs } = Mixly; + const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); + try { + data = unescape(data.replace(/(_E[0-9A-F]{1}_[0-9A-F]{2}_[0-9A-F]{2})+/gm, '%$1')); + data = unescape(data.replace(/\\(u[0-9a-fA-F]{4})/gm, '%$1')); + } catch (error) { + Debug.error(error); + } + statusBarTerminal.addValue(data); + }); } - layer.msg(message, { time: 1000 }); + + this.initCompile = function () { + if (!this.mixlySocket.isConnected()) { + layer.msg(Msg.Lang['websocket.offline'], { time: 1000 }); + return; + } + const { mainStatusBarTabs } = Mixly; + const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); + mainStatusBarTabs.changeTo('output'); + mainStatusBarTabs.show(); + const mainWorkspace = Workspace.getMain(); + const editor = mainWorkspace.getEditorsManager().getActive(); + const code = editor.getCode(); + statusBarTerminal.setValue(`${Msg.Lang['shell.compiling']}...\n`); + this.shell.compile(code) + .then((info) => { + this.endCallback(info.code, info.time); + }) + .catch((error) => { + Debug.error(error); + statusBarTerminal.addValue(`\n==${Msg.Lang['shell.compileFailed']}==\n`); + }); + } + + this.initUpload = function () { + // 委托给 WebCompiler.ArduShell 处理本地上传(使用 AVRUploader 或 esptool-js) + // 服务器无法访问用户本地的串口设备,必须在浏览器端完成上传 + const WebCompilerArduShell = getWebCompilerArduShell(); + if (WebCompilerArduShell) { + return WebCompilerArduShell.initUpload(); + } + + if (!this.mixlySocket.isConnected()) { + layer.msg(Msg.Lang['websocket.offline'], { time: 1000 }); + return; + } + const port = Serial.getSelectedPortName(); + if (!port) { + layer.msg(Msg.Lang['statusbar.serial.noDevice'], { + time: 1000 + }); + return; + } + const { mainStatusBarTabs } = Mixly; + const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); + mainStatusBarTabs.changeTo('output'); + mainStatusBarTabs.show(); + statusBarTerminal.setValue(`${Msg.Lang['shell.uploading']}...\n`); + const mainWorkspace = Workspace.getMain(); + const editor = mainWorkspace.getEditorsManager().getActive(); + const code = editor.getCode(); + const statusBarSerial = mainStatusBarTabs.getStatusBarById(port); + const closePromise = statusBarSerial ? statusBarSerial.close() : Promise.resolve(); + closePromise + .then(() => { + return this.shell.upload(port, code) + }) + .then((info) => { + this.endCallback(info.code, info.time); + if (info.code || !Serial.portIsLegal(port)) { + return; + } + mainStatusBarTabs.add('serial', port); + mainStatusBarTabs.changeTo(port); + const statusBarSerial = mainStatusBarTabs.getStatusBarById(port); + statusBarSerial.open() + .then(() => { + const baudRates = code.match(/(?<=Serial.begin[\s]*\([\s]*)[0-9]*(?=[\s]*\))/g); + if (!baudRates?.length) { + return statusBarSerial.setBaudRate(9600); + } else { + return statusBarSerial.setBaudRate(baudRates[0] - 0); + } + }) + .catch(Debug.error); + }) + .catch((error) => { + Debug.error(error); + statusBarTerminal.addValue(`\n==${Msg.Lang['shell.uploadFailed']}==\n`); + }); + } + + this.endCallback = function (code, time) { + const { mainStatusBarTabs } = Mixly; + const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); + mainStatusBarTabs.changeTo('output'); + let message = ''; + if (code) { + message = (this.shell.isCompiling() ? Msg.Lang['shell.compileFailed'] : Msg.Lang['shell.uploadFailed']); + statusBarTerminal.addValue(`\n==${message}==\n`); + } else { + message = (this.shell.isCompiling() ? Msg.Lang['shell.compileSucc'] : Msg.Lang['shell.uploadSucc']); + statusBarTerminal.addValue(`\n==${message}(${Msg.Lang['shell.timeCost']} ${dayjs.duration(time).format('HH:mm:ss.SSS') + })==\n`); + } + layer.msg(message, { time: 1000 }); + } + } + + #running_ = false; + #upload_ = false; + #killing_ = false; + #layer_ = null; + + constructor() { + this.#layer_ = new LayerProgress({ + width: 200, + cancelValue: Msg.Lang['nav.btn.stop'], + skin: 'layui-anim layui-anim-scale', + cancel: () => { + if (this.#killing_) { + return false; + } + this.#layer_.title(`${Msg.Lang['shell.aborting']}...`); + this.#killing_ = true; + this.kill().catch(Debug.error); + return false; + }, + cancelDisplay: false + }); + } + + async compile(code) { + return new Promise(async (resolve, reject) => { + this.#running_ = true; + this.#upload_ = false; + this.#killing_ = false; + this.showProgress(); + const key = Boards.getSelectedBoardCommandParam(); + const config = { key, code }; + const mixlySocket = WebSocketArduShell.getMixlySocket(); + mixlySocket.emit('arduino.compile', config, (response) => { + this.hideProgress(); + if (response.error) { + reject(response.error); + return; + } + const [error, result] = response; + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + } + + async upload(port, code) { + return new Promise(async (resolve, reject) => { + this.#running_ = true; + this.#upload_ = true; + this.#killing_ = false; + this.showProgress(); + const key = Boards.getSelectedBoardCommandParam(); + const config = { key, code, port }; + const mixlySocket = WebSocketArduShell.getMixlySocket(); + mixlySocket.emit('arduino.upload', config, (response) => { + this.hideProgress(); + if (response.error) { + reject(response.error); + return; + } + const [error, result] = response; + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + } + + async kill() { + return new Promise(async (resolve, reject) => { + const mixlySocket = WebSocketArduShell.getMixlySocket(); + mixlySocket.emit('arduino.kill', (response) => { + if (response.error) { + reject(response.error); + return; + } + const [error, result] = response; + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + } + + showProgress() { + const message = this.isCompiling() ? Msg.Lang['shell.compiling'] : Msg.Lang['shell.uploading']; + this.#layer_.title(`${message}...`); + this.#layer_.show(); + } + + hideProgress() { + this.#layer_.hide(); + } + + isUploading() { + return this.#running_ && this.#upload_; + } + + isCompiling() { + return this.#running_ && !this.#upload_; } } - #running_ = false; - #upload_ = false; - #killing_ = false; - #layer_ = null; - - constructor() { - this.#layer_ = new LayerProgress({ - width: 200, - cancelValue: Msg.Lang['nav.btn.stop'], - skin: 'layui-anim layui-anim-scale', - cancel: () => { - if (this.#killing_) { - return false; - } - this.#layer_.title(`${Msg.Lang['shell.aborting']}...`); - this.#killing_ = true; - this.kill().catch(Debug.error); - return false; - }, - cancelDisplay: false - }); - } - - async compile(code) { - return new Promise(async (resolve, reject) => { - this.#running_ = true; - this.#upload_ = false; - this.#killing_ = false; - this.showProgress(); - const key = Boards.getSelectedBoardCommandParam(); - const config = { key, code }; - const mixlySocket = WebSocketArduShell.getMixlySocket(); - mixlySocket.emit('arduino.compile', config, (response) => { - this.hideProgress(); - if (response.error) { - reject(response.error); - return; - } - const [error, result] = response; - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); - } - - async upload(port, code) { - return new Promise(async (resolve, reject) => { - this.#running_ = true; - this.#upload_ = true; - this.#killing_ = false; - this.showProgress(); - const key = Boards.getSelectedBoardCommandParam(); - const config = { key, code, port }; - const mixlySocket = WebSocketArduShell.getMixlySocket(); - mixlySocket.emit('arduino.upload', config, (response) => { - this.hideProgress(); - if (response.error) { - reject(response.error); - return; - } - const [error, result] = response; - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); - } - - async kill() { - return new Promise(async (resolve, reject) => { - const mixlySocket = WebSocketArduShell.getMixlySocket(); - mixlySocket.emit('arduino.kill', (response) => { - if (response.error) { - reject(response.error); - return; - } - const [error, result] = response; - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); - } - - showProgress() { - const message = this.isCompiling() ? Msg.Lang['shell.compiling'] : Msg.Lang['shell.uploading']; - this.#layer_.title(`${message}...`); - this.#layer_.show(); - } - - hideProgress() { - this.#layer_.hide(); - } - - isUploading() { - return this.#running_ && this.#upload_; - } - - isCompiling() { - return this.#running_ && !this.#upload_; - } -} - -WebSocket.ArduShell = WebSocketArduShell; + WebSocket.ArduShell = WebSocketArduShell; }); \ No newline at end of file