Update: WebSocket支持MicroPython板卡烧录固件及上传程序

This commit is contained in:
王立帮
2024-12-02 21:50:56 +08:00
parent 4008e1aab5
commit a5c1ef4269
14 changed files with 379 additions and 521 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -10,11 +10,17 @@ export default function addBoardFSItem () {
menu.add({
weight: 2,
type: 'sep1',
preconditionFn: () => {
return goog.isElectron;
},
data: '---------'
});
menu.add({
weight: 3,
type: 'filesystem-tool',
preconditionFn: () => {
return goog.isElectron;
},
data: {
isHtmlName: true,
name: ContextMenu.getItem(Msg.BOARD_FS, ''),

View File

@@ -10,11 +10,17 @@ export default function addBoardFSItem () {
menu.add({
weight: 2,
type: 'sep1',
preconditionFn: () => {
return goog.isElectron;
},
data: '---------'
});
menu.add({
weight: 3,
type: 'filesystem-tool',
preconditionFn: () => {
return goog.isElectron;
},
data: {
isHtmlName: true,
name: ContextMenu.getItem(Msg.BOARD_FS, ''),

View File

@@ -30,6 +30,7 @@ goog.require('Mixly.Web.Serial');
goog.require('Mixly.WebSocket.File');
goog.require('Mixly.WebSocket.Serial');
goog.require('Mixly.WebSocket.ArduShell');
goog.require('Mixly.WebSocket.BU');
goog.provide('Mixly.App');
const {
@@ -181,7 +182,7 @@ class App extends Component {
id: 'arduino-compile-btn',
displayText: Msg.Lang['nav.btn.compile'],
preconditionFn: () => {
if (!goog.isElectron && !Env.hasSocketServer && !env.hasCompiler) {
if (!goog.isElectron && !Env.hasSocketServer && !Env.hasCompiler) {
return false;
}
if (!SELECTED_BOARD?.nav?.compile || !SELECTED_BOARD?.nav?.upload) {
@@ -209,7 +210,7 @@ class App extends Component {
id: 'arduino-upload-btn',
displayText: Msg.Lang['nav.btn.upload'],
preconditionFn: () => {
if (!goog.isElectron && !Env.hasSocketServer && !env.hasCompiler) {
if (!goog.isElectron && !Env.hasSocketServer && !Env.hasCompiler) {
return false;
}
if (!SELECTED_BOARD?.nav?.compile || !SELECTED_BOARD?.nav?.upload) {

View File

@@ -244,7 +244,9 @@ Boards.changeTo = (boardName) => {
outObj.volume = "CIRCUITPY";
}
}
outObj.filePath = MString.tpl(outObj.filePath, pathObj);
if (!Env.hasSocketServer) {
outObj.filePath = MString.tpl(outObj.filePath, pathObj);
}
break;
case 'command':
let pyToolsPath = "{srcPath}/tools/python/";
@@ -256,8 +258,10 @@ Boards.changeTo = (boardName) => {
'stm32bl': 'stm32bl.py',
'ampy': 'ampy/cli.py'
};
for (let key in pyTools) {
obj[key] = Env.python3Path + "\" \"" + pyToolsPath + pyTools[key];
if (!Env.hasSocketServer) {
for (let key in pyTools) {
obj[key] = Env.python3Path + "\" \"" + pyToolsPath + pyTools[key];
}
}
if (outObj.reset) {
let resetStr = '{}';
@@ -270,7 +274,9 @@ Boards.changeTo = (boardName) => {
}
}
outObj.command = MString.tpl(outObj.command, obj);
outObj.command = MString.tpl(outObj.command, pathObj);
if (!Env.hasSocketServer) {
outObj.command = MString.tpl(outObj.command, pathObj);
}
if (outObj.special && outObj.special instanceof Array) {
for (let key in outObj.special) {
if (!outObj.special[key]?.name
@@ -278,23 +284,31 @@ Boards.changeTo = (boardName) => {
continue;
}
outObj.special[key].command = MString.tpl(outObj.special[key].command, obj);
outObj.special[key].command = MString.tpl(outObj.special[key].command, pathObj);
if (!Env.hasSocketServer) {
outObj.special[key].command = MString.tpl(outObj.special[key].command, pathObj);
}
}
}
break;
}
if (value.type === 'upload' && (goog.isElectron || Env.hasSocketServer) && outObj.copyLib) {
if (outObj.libPath) {
let libPath = [];
for (let dirPath of outObj.libPath) {
libPath.push(MString.tpl(dirPath, pathObj));
if (!Env.hasSocketServer) {
let libPath = [];
for (let dirPath of outObj.libPath) {
libPath.push(MString.tpl(dirPath, pathObj));
}
outObj.libPath = libPath;
}
outObj.libPath = libPath;
} else {
outObj.libPath = [ path.join(Env.boardDirPath, 'build/lib/') ];
if (Env.hasSocketServer) {
outObj.libPath = [ 'build/lib/' ];
} else {
outObj.libPath = [ path.join(Env.boardDirPath, 'build/lib/') ];
}
}
}
if (value.type === 'upload' && (goog.isElectron || Env.hasSocketServer)) {
if (value.type === 'upload' && (goog.isElectron && !Env.hasSocketServer)) {
if (outObj.filePath) {
outObj.filePath = MString.tpl(outObj.filePath, pathObj);
} else {

View File

@@ -264,7 +264,11 @@ class StatusBarSerial extends PageBase {
this.stopRead();
this.#timer_ && clearTimeout(this.#timer_);
this.#timer_ = null;
if (this.isDisposed() || !this.isOpened()) {
if (this.isDisposed()) {
return;
}
if (!this.isOpened()) {
this.setValue(`${String(error)}\n`);
return;
}
this.setValue(`${this.getValue() + this.#valueTemp_}\n${String(error)}\n`);

View File

@@ -49,7 +49,8 @@
"Mixly.Web.Serial",
"Mixly.WebSocket.File",
"Mixly.WebSocket.Serial",
"Mixly.WebSocket.ArduShell"
"Mixly.WebSocket.ArduShell",
"Mixly.WebSocket.BU"
],
"provide": [
"Mixly.App"
@@ -1736,15 +1737,13 @@
"path": "/web-socket/burn-upload.js",
"require": [
"layui",
"Mixly.Config",
"dayjs.duration",
"Mixly.Debug",
"Mixly.LayerExt",
"Mixly.Env",
"Mixly.Boards",
"Mixly.MFile",
"Mixly.MString",
"Mixly.Msg",
"Mixly.WebSocket.Serial",
"Mixly.WebSocket.Socket"
"Mixly.Config",
"Mixly.Workspace",
"Mixly.WebSocket.Serial"
],
"provide": [
"Mixly.WebSocket.BU"
@@ -1780,7 +1779,8 @@
"Mixly.StatusBarsManager",
"Mixly.WebSocket",
"Mixly.WebSocket.Serial",
"Mixly.WebSocket.ArduShell"
"Mixly.WebSocket.ArduShell",
"Mixly.WebSocket.BU"
],
"provide": [
"Mixly.WebSocket.Socket"

View File

@@ -57,6 +57,10 @@ class WebSocketArduShell {
}
this.initCompile = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg('服务端已离线', { time: 1000 });
return;
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output');
@@ -71,17 +75,27 @@ class WebSocketArduShell {
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`==${Msg.Lang['shell.compileFailed']}==\n`);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.compileFailed']}==\n`);
});
}
this.initUpload = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg('服务端已离线', { 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 port = Serial.getSelectedPortName();
const mainWorkspace = Workspace.getMain();
const editor = mainWorkspace.getEditorsManager().getActive();
const code = editor.getCode();
@@ -98,6 +112,7 @@ class WebSocketArduShell {
}
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);
@@ -111,7 +126,7 @@ class WebSocketArduShell {
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadFailed']}==\n`);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.uploadFailed']}==\n`);
});
}

View File

@@ -1,506 +1,278 @@
goog.loadJs('web', () => {
goog.require('layui');
goog.require('Mixly.Config');
goog.require('dayjs.duration');
goog.require('Mixly.Debug');
goog.require('Mixly.LayerExt');
goog.require('Mixly.Env');
goog.require('Mixly.Boards');
goog.require('Mixly.MFile');
goog.require('Mixly.MString');
goog.require('Mixly.Msg');
goog.require('Mixly.Env');
goog.require('Mixly.Config');
goog.require('Mixly.Workspace');
goog.require('Mixly.WebSocket.Serial');
goog.require('Mixly.WebSocket.Socket');
goog.provide('Mixly.WebSocket.BU');
const {
Config,
Debug,
LayerExt,
Config,
Msg,
Env,
Boards,
MFile,
MString,
Msg
Workspace,
WebSocket
} = Mixly;
const { BU, Serial, Socket } = Mixly.WebSocket;
const { BOARD, SELECTED_BOARD } = Config;
const { SELECTED_BOARD } = Config;
const { form } = layui;
const { Serial } = WebSocket;
BU.uploading = false;
BU.burning = false;
class WebSocketBU {
static {
this.mixlySocket = null;
this.socket = null;
this.shell = null;
BU.shell = null;
/**
* @function 根据传入的stdout判断磁盘数量并选择对应操作
* @param type {string} 值为'burn' | 'upload'
* @param stdout {string} 磁盘名称字符串,形如'G:K:F:'
* @param startPath {string} 需要拷贝的文件路径
* @return {void}
**/
BU.checkNumOfDisks = function (type, stdout, startPath) {
let wmicResult = stdout;
wmicResult = wmicResult.replace(/\s+/g, "");
wmicResult = wmicResult.replace("DeviceID", "");
// wmicResult = 'G:K:F:';
let result = wmicResult.split(':');
let pathAdd = (Env.currentPlatform === "win32") ? ':' : '';
if (stdout.indexOf(":") != stdout.lastIndexOf(":")) {
let form = layui.form;
let devicesName = $('#mixly-selector-type');
let oldDevice = $('#mixly-selector-type option:selected').val();
devicesName.empty();
for (let i = 0; i < result.length; i++) {
if (result[i]) {
if (oldDevice == result[i] + pathAdd) {
devicesName.append('<option value="' + result[i] + pathAdd + '" selected>' + result[i] + pathAdd + '</option>');
} else {
devicesName.append('<option value="' + result[i] + pathAdd + '">' + result[i] + pathAdd + '</option>');
}
}
this.getSocket = function () {
return this.socket;
}
form.render();
let initBtnClicked = false;
const layerNum = layer.open({
type: 1,
id: "serial-select",
title: Msg.Lang['检测到多个同类型设备,请选择:'],
area: ['350px', '150px'],
content: $('#mixly-selector-div'),
shade: LayerExt.SHADE_ALL,
resize: false,
closeBtn: 0,
success: function (layero) {
$('#serial-select').css('height', '195px');
$(".layui-layer-page").css("z-index","198910151");
$("#mixly-selector-btn1").off("click").click(() => {
layer.close(layerNum);
BU.cancel();
});
$("#mixly-selector-btn2").off("click").click(() => {
layer.close(layerNum);
initBtnClicked = true;
});
},
end: function () {
$('#mixly-selector-div').css('display', 'none');
$("#layui-layer-shade" + layerNum).remove();
if (initBtnClicked) {
BU.initWithDropdownBox(type, startPath);
}
$("#mixly-selector-btn1").off("click");
$("#mixly-selector-btn2").off("click");
}
});
} else {
const layerNum = layer.open({
type: 1,
title: (type === 'burn'? Msg.Lang['shell.burning'] : Msg.Lang['shell.uploading']) + '...',
content: $('#mixly-loader-div'),
shade: LayerExt.SHADE_NAV,
resize: false,
closeBtn: 0,
success: function (layero, index) {
BU.copyFiles(type, index, startPath, result[0] + pathAdd + '/');
$("#mixly-loader-btn").off("click").click(() => {
layer.close(index);
BU.cancel();
});
},
end: function () {
$('#mixly-selector-div').css('display', 'none');
$("#layui-layer-shade" + layerNum).remove();
$("#mixly-loader-btn").off("click");
}
});
}
}
BU.copyFiles = (type, layerNum, startPath, desPath) => {
const code = MFile.getCode();
const {
copyLib = false,
libPath = []
} = SELECTED_BOARD.upload;
Socket.sendCommand({
obj: 'BU',
func: 'copyFiles',
args: [ type, layerNum, startPath, desPath, code, copyLib, libPath ]
});
}
BU.operateSuccess = (type, layerNum, port) => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
layer.close(layerNum);
const message = (type === 'burn'? Msg.Lang['shell.burnSucc'] : Msg.Lang['shell.uploadSucc']);
layer.msg(message, {
time: 1000
});
const value = statusBarTerminal.getValue();
let prefix = '';
if (value.lastIndexOf('\n') !== value.length - 1) {
prefix = '\n';
}
statusBarTerminal.addValue(prefix + `==${message}==\n`);
if (type === 'upload' && (Serial.uploadPorts.length === 1 || port)) {
Serial.connect(port ?? Serial.uploadPorts[0].name, null);
}
BU.burning = false;
BU.uploading = false;
}
BU.operateError = (type, layerNum, error) => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
layer.close(layerNum);
const value = statusBarTerminal.getValue();
let prefix = '';
if (value.lastIndexOf('\n') !== value.length - 1) {
prefix = '\n';
}
statusBarTerminal.addValue(prefix + error + '\n');
console.log(error);
BU.burning = false;
BU.uploading = false;
}
BU.noDevice = () => {
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});
BU.burning = false;
BU.uploading = false;
}
/**
* @function 判断当前环境,以开始一个上传过程
* @param type {string} 值为'burn' | 'upload'
* @param startPath {string} 需要拷贝的文件或文件夹的路径
* @return {void}
*/
BU.initWithDropdownBox = function (type, startPath) {
const layerNum = layer.open({
type: 1,
title: (type === 'burn'? Msg.Lang['shell.burning'] : Msg.Lang['shell.uploading']) + '...',
content: $('#mixly-loader-div'),
shade: LayerExt.SHADE_NAV,
resize: false,
closeBtn: 0,
success: function (layero, index) {
$(".layui-layer-page").css("z-index","198910151");
$("#mixly-loader-btn").off("click").click(() => {
layer.close(index);
BU.cancel();
});
const desPath = $('#mixly-selector-type option:selected').val();
BU.copyFiles(type, index, startPath, desPath);
},
end: function () {
$('#mixly-loader-div').css('display', 'none');
$("#layui-layer-shade" + layerNum).remove();
this.getMixlySocket = function () {
return this.mixlySocket;
}
});
}
/**
* @function 取消烧录或上传
* @return {void}
*/
BU.cancel = function () {
Socket.sendCommand({
obj: 'BU',
func: 'cancel',
args: []
});
if (BU.uploading) {
BU.uploading = false;
layer.msg(Msg.Lang['shell.uploadCanceled'], {
time: 1000
});
} else if (BU.burning) {
BU.burning = false;
layer.msg(Msg.Lang['shell.burnCanceled'], {
time: 1000
});
}
}
this.init = function (mixlySocket) {
this.mixlySocket = mixlySocket;
this.socket = mixlySocket.getSocket();
this.shell = new WebSocketBU();
const socket = this.socket;
/**
* @function 开始一个烧录过程
* @return {void}
*/
BU.initBurn = function () {
Socket.connect((WS) => {
layer.closeAll();
}, () => {
if (BU.burning) return;
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
const { burn } = SELECTED_BOARD;
statusBarTerminal.setValue('');
mainStatusBarTabs.changeTo("output");
mainStatusBarTabs.show();
BU.burning = true;
BU.uploading = false;
if (burn.type === 'volume') {
Socket.sendCommand({
obj: 'BU',
func: 'getDisksWithVolumesName',
args: [ 'burn', burn.volume, burn.filePath ]
});
} else {
const port = Serial.getSelectedPortName();
BU.burnWithPort(port, burn.command);
}
});
}
/**
* @function 开始一个上传过程
* @return {void}
*/
BU.initUpload = function () {
Socket.connect((WS) => {
layer.closeAll();
}, () => {
if (BU.uploading) return;
const { upload } = SELECTED_BOARD;
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.setValue('');
mainStatusBarTabs.changeTo("output");
mainStatusBarTabs.show();
BU.burning = false;
BU.uploading = true;
if (upload.type === "volume") {
Socket.sendCommand({
obj: 'BU',
func: 'getDisksWithVolumesName',
args: [ 'upload', upload.volume, upload.filePath ]
});
} else {
const port = Serial.getSelectedPortName();
BU.uploadWithPort(port, upload.command);
}
});
}
/**
* @function 通过cmd烧录
* @param layerNum {number} 烧录或上传加载弹窗的编号,用于关闭此弹窗
* @param port {string} 所选择的串口
* @param command {string} 需要执行的指令
* @return {void}
*/
BU.burnByCmd = function (layerNum, port, command) {
const newCommand = MString.tpl(command, { com: port });
Socket.sendCommand({
obj: 'BU',
func: 'burnByCmd',
args: [ layerNum, port, newCommand ]
});
}
/**
* @function 通过cmd上传
* @param layerNum {number} 烧录或上传加载弹窗的编号,用于关闭此弹窗
* @param port {string} 所选择的串口
* @param command {string} 需要执行的指令
* @return {void}
*/
BU.uploadByCmd = function (layerNum, port, command) {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
const newCommand = MString.tpl(command, { com: port });
const { upload } = SELECTED_BOARD;
const {
filePath,
copyLib = false,
libPath = []
} = upload;
const code = MFile.getCode();
statusBarTerminal.addValue(Msg.Lang['shell.uploading'] + '...\n');
Socket.sendCommand({
obj: 'BU',
func: 'uploadByCmd',
args: [ layerNum, port, newCommand, code, filePath, copyLib, libPath ]
});
}
/**
* @function 特殊固件的烧录
* @return {void}
**/
BU.burnWithSpecialBin = () => {
const devNames = $('#mixly-selector-type');
let oldDevice = $('#mixly-selector-type option:selected').val();
devNames.empty();
let firmwareList = BOARD.burn.special;
let firmwareObj = {};
for (let i = 0; i < firmwareList.length; i++)
firmwareObj[firmwareList[i].name] = firmwareList[i].command;
firmwareList.map(firmware => {
if (!firmware?.name && !firmware?.command) return;
if (`${firmware.name}` == oldDevice) {
devNames.append($(`<option value="${firmware.name}" selected>${firmware.name}</option>`));
} else {
devNames.append($(`<option value="${firmware.name}">${firmware.name}</option>`));
}
});
form.render();
let initBtnClicked = false;
const layerNum = layer.open({
type: 1,
id: "serial-select",
title: "请选择固件:",
area: ['350px', '150px'],
content: $('#mixly-selector-div'),
shade: Mixly.LayerExt.SHADE_ALL,
resize: false,
closeBtn: 0,
success: function (layero) {
$('#serial-select').css('height', '180px');
$('#serial-select').css('overflow', 'inherit');
$(".layui-layer-page").css("z-index", "198910151");
$("#mixly-selector-btn1").off("click").click(() => {
layer.close(layerNum);
});
$("#mixly-selector-btn2").click(() => {
layer.close(layerNum);
initBtnClicked = true;
});
},
end: function () {
$("#mixly-selector-btn1").off("click");
$("#mixly-selector-btn2").off("click");
$('#mixly-selector-div').css('display', 'none');
$(".layui-layer-shade").remove();
if (initBtnClicked) {
let selectedFirmwareName = $('#mixly-selector-type option:selected').val();
try {
firmwareObj[selectedFirmwareName] = firmwareObj[selectedFirmwareName].replace(/\\/g, "/");
} catch (e) {
console.log(e);
}
let pyToolName = ["esptool", "kflash", "stm32loader", "stm32bl"];
let pyToolPath = "{path}/mixpyBuild/win_python3/Lib/site-packages/"
if (Env.currentPlatform == "darwin" || Env.currentPlatform == "linux") {
pyToolPath = "{path}/tools/python/";
}
for (let i = 0; i < pyToolName.length; i++) {
if (firmwareObj[selectedFirmwareName].indexOf("\"") != -1) {
firmwareObj[selectedFirmwareName] = replaceWithReg(firmwareObj[selectedFirmwareName], Env.python3Path + "\" \"" + pyToolPath + pyToolName[i] + ".py", pyToolName[i]);
} else {
firmwareObj[selectedFirmwareName] = replaceWithReg(firmwareObj[selectedFirmwareName], Env.python3Path + " " + pyToolPath + pyToolName[i] + ".py", pyToolName[i]);
}
}
firmwareObj[selectedFirmwareName] = replaceWithReg(firmwareObj[selectedFirmwareName], Env.clientPath, "path");
firmwareObj[selectedFirmwareName] = replaceWithReg(firmwareObj[selectedFirmwareName], Env.boardDirPath, "indexPath");
socket.on('micropython.dataEvent', (data) => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.setValue('');
mainStatusBarTabs.changeTo("output");
mainStatusBarTabs.show();
BU.burning = true;
BU.uploading = false;
const port = Serial.getSelectedPortName();
BU.burnWithPort(port, firmwareObj[selectedFirmwareName]);
} else {
layer.msg(Msg.Lang['shell.burnCanceled'], { time: 1000 });
}
statusBarTerminal.addValue(data);
});
socket.on('micropython.errorEvent', (data) => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.addValue(data);
});
}
});
}
/**
* @function 通过串口执行命令行烧录或上传操作
* @param type {string} 值为 'burn' | 'upload'
* @param port {string} 所选择的串口
* @param command {string} 需要执行的指令
* @return {void}
**/
BU.operateWithPort = (type, port, command) => {
if (!port) {
layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});
BU.burning = false;
BU.uploading = false;
return;
}
const title = (type === 'burn' ? Msg.Lang['shell.burning'] : Msg.Lang['shell.uploading']) + '...';
const operate = () => {
const layerNum = layer.open({
type: 1,
title,
content: $('#mixly-loader-div'),
shade: LayerExt.SHADE_NAV,
resize: false,
closeBtn: 0,
success: function (layero, index) {
$(".layui-layer-page").css("z-index","198910151");
switch (type) {
case 'burn':
BU.burnByCmd(index, port, command);
break;
case 'upload':
default:
BU.uploadByCmd(index, port, command);
}
$("#mixly-loader-btn").off("click").click(() => {
$("#mixly-loader-btn").css('display', 'none');
switch (type) {
case 'burn':
layer.title(Msg.Lang['shell.aborting'] + '...', index);
break;
case 'upload':
default:
layer.title(Msg.Lang['shell.aborting'] + '...', index);
}
BU.cancel(type);
});
},
end: function () {
$('#mixly-loader-div').css('display', 'none');
$("layui-layer-shade" + layerNum).remove();
$("#mixly-loader-btn").off("click");
$("#mixly-loader-btn").css('display', 'inline-block');
this.initBurn = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg('服务端已离线', { 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.burning']}...\n`);
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
const closePromise = statusBarSerial ? statusBarSerial.close() : Promise.resolve();
closePromise
.then(() => {
return this.shell.burn(port);
})
.then((info) => {
this.endCallback(info.code, info.time);
})
.catch((error) => {
Debug.error(error);
statusBarTerminal.addValue(`\n==${Msg.Lang['shell.burnFailed']}==\n`);
});
}
this.initUpload = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg('服务端已离线', { 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(() => {
return statusBarSerial.setBaudRate(115200);
})
.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.isBurning() ? Msg.Lang['shell.burnFailed'] : Msg.Lang['shell.uploadFailed']);
statusBarTerminal.addValue(`\n==${message}==\n`);
} else {
message = (this.shell.isBurning() ? Msg.Lang['shell.burnSucc'] : 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;
#layerNum_ = null;
constructor() {}
async burn(port) {
return new Promise(async (resolve, reject) => {
this.#running_ = true;
this.#upload_ = false;
await this.showProgress();
const config = {
boardDirPath: `.${Env.boardDirPath}`,
port,
command: SELECTED_BOARD.burn.command
};
const mixlySocket = WebSocketBU.getMixlySocket();
mixlySocket.emit('micropython.burn', config, (response) => {
this.hideProgress();
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
operate();
async upload(port, code) {
return new Promise(async (resolve, reject) => {
this.#running_ = true;
this.#upload_ = true;
await this.showProgress();
const config = {
boardDirPath: `.${Env.boardDirPath}`,
command: SELECTED_BOARD.upload.command,
libPath: SELECTED_BOARD.upload.libPath,
filePath: SELECTED_BOARD.upload.filePath,
port, code
};
const mixlySocket = WebSocketBU.getMixlySocket();
mixlySocket.emit('micropython.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 = WebSocketBU.getMixlySocket();
mixlySocket.emit('micropython.kill', (response) => {
if (response.error) {
reject(response.error);
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
async showProgress() {
return new Promise((resolve, reject) => {
this.#layerNum_ = layer.open({
type: 1,
title: `${this.isCompiling ? Msg.Lang['shell.burning'] : Msg.Lang['shell.uploading']}...`,
content: $('#mixly-loader-div'),
shade: LayerExt.SHADE_NAV,
resize: false,
closeBtn: 0,
success: () => {
$('#mixly-loader-btn').off('click').click(() => {
$('#mixly-loader-btn').css('display', 'none');
layer.title(`${Msg.Lang['shell.aborting']}...`, this.layerNum);
this.kill().catch(Debug.error);
});
resolve();
},
end: function () {
$('#mixly-loader-btn').off('click');
$('#mixly-loader-btn').css('display', 'inline-block');
}
});
})
}
hideProgress() {
layer.close(this.#layerNum_);
}
isUploading() {
return this.#running_ && this.#upload_;
}
isBurning() {
return this.#running_ && !this.#upload_;
}
}
/**
* @function 通过串口执行命令行烧录操作
* @param port {string} 所选择的串口
* @param command {string} 需要执行的指令
* @return {void}
**/
BU.burnWithPort = (port, command) => {
BU.operateWithPort('burn', port, command);
}
/**
* @function 通过串口执行命令行上传操作
* @param port {string} 所选择的串口
* @param command {string} 需要执行的指令
* @return {void}
**/
BU.uploadWithPort = (port, command) => {
BU.operateWithPort('upload', port, command);
}
BU.addValue = function (data) {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.addValue(data);
}
WebSocket.BU = WebSocketBU;
});

View File

@@ -157,7 +157,7 @@ class WebSocketSerial extends Serial {
this.onString(str);
});
eventRegistry.register(`${port}-error`, (error) => {
this.onError(String(error));
this.onError(error);
this.onClose(1);
});
eventRegistry.register(`${port}-open`, () => {
@@ -324,6 +324,24 @@ class WebSocketSerial extends Serial {
}
}
async #awaitDispose_() {
return new Promise((resolve, reject) => {
const mixlySocket = WebSocketSerial.getMixlySocket();
mixlySocket.emit('serial.dispose', this.getPortName(), (response) => {
if (response.error) {
resolve();
return;
}
const [error, result] = response;
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
async dispose() {
return new Promise((resolve, reject) => {
const port = this.getPortName();
@@ -335,18 +353,7 @@ class WebSocketSerial extends Serial {
eventRegistry.unregister(`${port}-close`);
super.dispose()
.then(() => {
const mixlySocket = WebSocketSerial.getMixlySocket();
mixlySocket.emit('serial.dispose', port, ([error, result]) => {
if (response.error) {
resolve();
return;
}
if (error) {
reject(error);
} else {
resolve(result);
}
});
return this.#awaitDispose_();
})
.catch(reject);
})

View File

@@ -5,6 +5,7 @@ goog.require('Mixly.StatusBarsManager');
goog.require('Mixly.WebSocket');
goog.require('Mixly.WebSocket.Serial');
goog.require('Mixly.WebSocket.ArduShell');
goog.require('Mixly.WebSocket.BU');
goog.provide('Mixly.WebSocket.Socket');
const {
@@ -16,7 +17,8 @@ const {
const {
Socket,
Serial,
ArduShell
ArduShell,
BU
} = WebSocket;
@@ -32,6 +34,7 @@ Socket.init = function () {
const socket = mixlySocket.getSocket();
socket.on('connect', () => {
Serial.getPorts()
.then((ports) => {
let portsName = [];
@@ -72,6 +75,7 @@ Socket.init = function () {
Serial.init(mixlySocket);
ArduShell.init(mixlySocket);
BU.init(mixlySocket);
}
});

View File

@@ -11,14 +11,42 @@ class WebSocket {
this.#socket_ = io(path, option);
}
#detectStatus_(status, callback) {
window.setTimeout(() => {
if (status.finished) {
return;
}
if (this.isConnected()) {
this.#detectStatus_(status, callback);
} else {
callback({
error: 'socket is not connected'
});
status.finished = true;
}
}, 1000);
}
emit(eventName, ...args) {
const callback = args.pop();
if (this.isConnected()) {
return this.#socket_.emit(eventName, ...args);
} else {
const callback = args.pop();
callback({
error: new Error('socket is not connected')
let emitStatus = {
finished: false
};
let status = this.#socket_.emit(eventName, ...args, (...callbackArgs) => {
if (emitStatus.finished) {
return;
}
emitStatus.finished = true;
callback(...callbackArgs);
});
this.#detectStatus_(emitStatus, callback);
return status;
} else {
callback({
error: 'socket is not connected'
});
return false;
}
}

View File

@@ -69,6 +69,7 @@ module.exports = {
'path': 'path',
'xscrollbar': 'XScrollbar',
'jquery': '$',
'ace': 'ace'
'ace': 'ace',
'goog': 'goog'
}
};