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({ menu.add({
weight: 2, weight: 2,
type: 'sep1', type: 'sep1',
preconditionFn: () => {
return goog.isElectron;
},
data: '---------' data: '---------'
}); });
menu.add({ menu.add({
weight: 3, weight: 3,
type: 'filesystem-tool', type: 'filesystem-tool',
preconditionFn: () => {
return goog.isElectron;
},
data: { data: {
isHtmlName: true, isHtmlName: true,
name: ContextMenu.getItem(Msg.BOARD_FS, ''), name: ContextMenu.getItem(Msg.BOARD_FS, ''),

View File

@@ -10,11 +10,17 @@ export default function addBoardFSItem () {
menu.add({ menu.add({
weight: 2, weight: 2,
type: 'sep1', type: 'sep1',
preconditionFn: () => {
return goog.isElectron;
},
data: '---------' data: '---------'
}); });
menu.add({ menu.add({
weight: 3, weight: 3,
type: 'filesystem-tool', type: 'filesystem-tool',
preconditionFn: () => {
return goog.isElectron;
},
data: { data: {
isHtmlName: true, isHtmlName: true,
name: ContextMenu.getItem(Msg.BOARD_FS, ''), 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.File');
goog.require('Mixly.WebSocket.Serial'); goog.require('Mixly.WebSocket.Serial');
goog.require('Mixly.WebSocket.ArduShell'); goog.require('Mixly.WebSocket.ArduShell');
goog.require('Mixly.WebSocket.BU');
goog.provide('Mixly.App'); goog.provide('Mixly.App');
const { const {
@@ -181,7 +182,7 @@ class App extends Component {
id: 'arduino-compile-btn', id: 'arduino-compile-btn',
displayText: Msg.Lang['nav.btn.compile'], displayText: Msg.Lang['nav.btn.compile'],
preconditionFn: () => { preconditionFn: () => {
if (!goog.isElectron && !Env.hasSocketServer && !env.hasCompiler) { if (!goog.isElectron && !Env.hasSocketServer && !Env.hasCompiler) {
return false; return false;
} }
if (!SELECTED_BOARD?.nav?.compile || !SELECTED_BOARD?.nav?.upload) { if (!SELECTED_BOARD?.nav?.compile || !SELECTED_BOARD?.nav?.upload) {
@@ -209,7 +210,7 @@ class App extends Component {
id: 'arduino-upload-btn', id: 'arduino-upload-btn',
displayText: Msg.Lang['nav.btn.upload'], displayText: Msg.Lang['nav.btn.upload'],
preconditionFn: () => { preconditionFn: () => {
if (!goog.isElectron && !Env.hasSocketServer && !env.hasCompiler) { if (!goog.isElectron && !Env.hasSocketServer && !Env.hasCompiler) {
return false; return false;
} }
if (!SELECTED_BOARD?.nav?.compile || !SELECTED_BOARD?.nav?.upload) { if (!SELECTED_BOARD?.nav?.compile || !SELECTED_BOARD?.nav?.upload) {

View File

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

View File

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

View File

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

View File

@@ -57,6 +57,10 @@ class WebSocketArduShell {
} }
this.initCompile = function () { this.initCompile = function () {
if (!this.mixlySocket.isConnected()) {
layer.msg('服务端已离线', { time: 1000 });
return;
}
const { mainStatusBarTabs } = Mixly; const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output'); mainStatusBarTabs.changeTo('output');
@@ -71,17 +75,27 @@ class WebSocketArduShell {
}) })
.catch((error) => { .catch((error) => {
Debug.error(error); Debug.error(error);
statusBarTerminal.addValue(`==${Msg.Lang['shell.compileFailed']}==\n`); statusBarTerminal.addValue(`\n==${Msg.Lang['shell.compileFailed']}==\n`);
}); });
} }
this.initUpload = function () { 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 { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
mainStatusBarTabs.changeTo('output'); mainStatusBarTabs.changeTo('output');
mainStatusBarTabs.show(); mainStatusBarTabs.show();
statusBarTerminal.setValue(`${Msg.Lang['shell.uploading']}...\n`); statusBarTerminal.setValue(`${Msg.Lang['shell.uploading']}...\n`);
const port = Serial.getSelectedPortName();
const mainWorkspace = Workspace.getMain(); const mainWorkspace = Workspace.getMain();
const editor = mainWorkspace.getEditorsManager().getActive(); const editor = mainWorkspace.getEditorsManager().getActive();
const code = editor.getCode(); const code = editor.getCode();
@@ -98,6 +112,7 @@ class WebSocketArduShell {
} }
mainStatusBarTabs.add('serial', port); mainStatusBarTabs.add('serial', port);
mainStatusBarTabs.changeTo(port); mainStatusBarTabs.changeTo(port);
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
statusBarSerial.open() statusBarSerial.open()
.then(() => { .then(() => {
const baudRates = code.match(/(?<=Serial.begin[\s]*\([\s]*)[0-9]*(?=[\s]*\))/g); const baudRates = code.match(/(?<=Serial.begin[\s]*\([\s]*)[0-9]*(?=[\s]*\))/g);
@@ -111,7 +126,7 @@ class WebSocketArduShell {
}) })
.catch((error) => { .catch((error) => {
Debug.error(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.loadJs('web', () => {
goog.require('layui'); goog.require('layui');
goog.require('Mixly.Config'); goog.require('dayjs.duration');
goog.require('Mixly.Debug');
goog.require('Mixly.LayerExt'); 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.Msg');
goog.require('Mixly.Env');
goog.require('Mixly.Config');
goog.require('Mixly.Workspace');
goog.require('Mixly.WebSocket.Serial'); goog.require('Mixly.WebSocket.Serial');
goog.require('Mixly.WebSocket.Socket');
goog.provide('Mixly.WebSocket.BU'); goog.provide('Mixly.WebSocket.BU');
const { const {
Config, Debug,
LayerExt, LayerExt,
Config,
Msg,
Env, Env,
Boards, Workspace,
MFile, WebSocket
MString,
Msg
} = Mixly; } = Mixly;
const { BU, Serial, Socket } = Mixly.WebSocket; const { SELECTED_BOARD } = Config;
const { BOARD, 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; this.getSocket = function () {
return this.socket;
/**
* @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>');
}
}
} }
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) => { this.getMixlySocket = function () {
const code = MFile.getCode(); return this.mixlySocket;
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.init = function (mixlySocket) {
* @function 取消烧录或上传 this.mixlySocket = mixlySocket;
* @return {void} this.socket = mixlySocket.getSocket();
*/ this.shell = new WebSocketBU();
BU.cancel = function () { const socket = this.socket;
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
});
}
}
/** socket.on('micropython.dataEvent', (data) => {
* @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");
const { mainStatusBarTabs } = Mixly; const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output'); const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.setValue(''); statusBarTerminal.addValue(data);
mainStatusBarTabs.changeTo("output"); });
mainStatusBarTabs.show();
BU.burning = true; socket.on('micropython.errorEvent', (data) => {
BU.uploading = false; const { mainStatusBarTabs } = Mixly;
const port = Serial.getSelectedPortName(); const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
BU.burnWithPort(port, firmwareObj[selectedFirmwareName]); statusBarTerminal.addValue(data);
} else { });
layer.msg(Msg.Lang['shell.burnCanceled'], { time: 1000 });
}
} }
});
}
/** this.initBurn = function () {
* @function 通过串口执行命令行烧录或上传操作 if (!this.mixlySocket.isConnected()) {
* @param type {string} 值为 'burn' | 'upload' layer.msg('服务端已离线', { time: 1000 });
* @param port {string} 所选择的串口 return;
* @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');
} }
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_;
}
} }
/** WebSocket.BU = WebSocketBU;
* @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);
}
}); });

View File

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

View File

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

View File

@@ -11,14 +11,42 @@ class WebSocket {
this.#socket_ = io(path, option); 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) { emit(eventName, ...args) {
const callback = args.pop();
if (this.isConnected()) { if (this.isConnected()) {
return this.#socket_.emit(eventName, ...args); let emitStatus = {
} else { finished: false
const callback = args.pop(); };
callback({ let status = this.#socket_.emit(eventName, ...args, (...callbackArgs) => {
error: new Error('socket is not connected') 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', 'path': 'path',
'xscrollbar': 'XScrollbar', 'xscrollbar': 'XScrollbar',
'jquery': '$', 'jquery': '$',
'ace': 'ace' 'ace': 'ace',
'goog': 'goog'
} }
}; };