Compare commits

...

11 Commits

4 changed files with 347 additions and 276 deletions

View File

@@ -1,32 +1,37 @@
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 {
const {
Boards,
Debug,
LayerExt,
Msg,
Workspace,
LayerProgress,
WebSocket
} = Mixly;
WebSocket,
WebCompiler = {}
} = Mixly;
const { Serial } = WebSocket;
// 动态获取 WebCompiler.ArduShell用于本地上传
const getWebCompilerArduShell = () => Mixly.WebCompiler?.ArduShell;
const { layer } = layui;
const { Serial } = WebSocket;
const { layer } = layui;
class WebSocketArduShell {
class WebSocketArduShell {
static {
this.mixlySocket = null;
this.socket = null;
@@ -46,6 +51,12 @@ class WebSocketArduShell {
this.shell = new WebSocketArduShell();
const socket = this.socket;
// 同时初始化 WebCompiler.ArduShell用于本地上传
const WebCompilerArduShell = getWebCompilerArduShell();
if (WebCompilerArduShell && !WebCompilerArduShell.getMixlySocket()) {
WebCompilerArduShell.init(mixlySocket);
}
socket.on('arduino.dataEvent', (data) => {
if (data.length > 1000) {
return;
@@ -64,6 +75,16 @@ class WebSocketArduShell {
} catch (error) {
Debug.error(error);
}
// 过滤掉无关紧要的端口错误(因为已经在本地完成上传了)
if (typeof data === 'string' && (
data.includes('cannot open serial1') ||
data.includes('cannot open device "undefined"') ||
data.includes('No such file or directory, cannot open')
)) {
return;
}
statusBarTerminal.addValue(data);
});
}
@@ -92,6 +113,13 @@ class WebSocketArduShell {
}
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;
@@ -152,8 +180,7 @@ class WebSocketArduShell {
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')
statusBarTerminal.addValue(`\n==${message}(${Msg.Lang['shell.timeCost']} ${dayjs.duration(time).format('HH:mm:ss.SSS')
})==\n`);
}
layer.msg(message, { time: 1000 });
@@ -268,8 +295,8 @@ class WebSocketArduShell {
isCompiling() {
return this.#running_ && !this.#upload_;
}
}
}
WebSocket.ArduShell = WebSocketArduShell;
WebSocket.ArduShell = WebSocketArduShell;
});

View File

@@ -31,8 +31,8 @@ goog.loadJs('web', () => {
const { SELECTED_BOARD, BOARD } = Config;
// 引入本地 Web.BU 模块,用于 MicroPython 本地上传
const WebBU = Mixly.Web?.BU;
// WebBU 需要在使用时动态获取,避免模块加载顺序问题
const getWebBU = () => Mixly.Web?.BU;
const { Serial } = WebSocket;
@@ -74,6 +74,7 @@ goog.loadJs('web', () => {
this.initBurn = function () {
// MicroPython 板卡使用本地 Web Serial API 烧录,而非通过服务器
const WebBU = getWebBU();
if (SELECTED_BOARD?.language === 'MicroPython' && WebBU) {
return WebBU.initBurn();
}
@@ -111,6 +112,7 @@ goog.loadJs('web', () => {
this.initUpload = function () {
// MicroPython 板卡使用本地 Web Serial API 上传,而非通过服务器
const WebBU = getWebBU();
if (SELECTED_BOARD?.language === 'MicroPython' && WebBU) {
return WebBU.initUpload();
}

View File

@@ -11,38 +11,60 @@ export default class ShellArduino extends Shell {
async compile(config) {
let arduino = _.merge({}, ARDUINO);
arduino = _.merge(arduino, config);
const command = [
`"${arduino.path.cli}"`,
// 构建参数数组,避免 shell 解析引号问题
const args = [
'compile',
'-b', arduino.key,
'--config-file', `"${arduino.path.config}"`,
'--verbose',
'--libraries', `"${arduino.path.libraries.join('","')}"`,
'--build-path', `"${arduino.path.build}"`,
'--output-dir', `"${arduino.path.output}"`,
`"${arduino.path.code}"`,
'--config-file', arduino.path.config,
'--verbose'
];
// 为每个 library 路径添加参数
// 注意config.json 中的路径是指向包含多个库的集合目录,所以必须用 --libraries
// 如果用 --libraryarduino-cli 会尝试把该目录当作单个库处理,导致找不到头文件报错
for (const lib of arduino.path.libraries) {
args.push('--libraries', lib);
}
args.push(
'--build-path', arduino.path.build,
'--output-dir', arduino.path.output,
arduino.path.code,
'--no-color'
].join(' ');
return this.execUntilClosed(command, { maxBuffer: 4096 * 1000000 });
);
return this.execFileUntilClosed(arduino.path.cli, args, { maxBuffer: 4096 * 1000000 });
}
async upload(config) {
let arduino = _.merge({}, ARDUINO);
arduino = _.merge(arduino, config);
const command = [
`"${arduino.path.cli}"`,
// 构建参数数组,避免 shell 解析引号问题
const args = [
'compile',
'--upload',
'-p', arduino.port,
'-b', arduino.key,
'--config-file', `"${arduino.path.config}"`,
'--verbose',
'--libraries', `"${arduino.path.libraries.join('","')}"`,
'--build-path', `"${arduino.path.build}"`,
'--output-dir', `"${arduino.path.output}"`,
`"${arduino.path.code}"`,
'--config-file', arduino.path.config,
'--verbose'
];
// 为每个 library 路径添加参数
// 注意config.json 中的路径是指向包含多个库的集合目录,所以必须用 --libraries
// 如果用 --libraryarduino-cli 会尝试把该目录当作单个库处理,导致找不到头文件报错
for (const lib of arduino.path.libraries) {
args.push('--libraries', lib);
}
args.push(
'--build-path', arduino.path.build,
'--output-dir', arduino.path.output,
arduino.path.code,
'--no-color'
].join(' ');
return this.execUntilClosed(command, { maxBuffer: 4096 * 1000000 });
);
return this.execFileUntilClosed(arduino.path.cli, args, { maxBuffer: 4096 * 1000000 });
}
}

View File

@@ -173,6 +173,12 @@ export default class Socket {
});
socket.on('micropython.burn', async (config, callback) => {
// 检查端口是否是有效的系统设备路径(而非前端内部标识符如 serial1
if (config.port && !config.port.startsWith('/dev/') && !config.port.includes(':')) {
console.log(`[MicroPython] 忽略无效端口请求: ${config.port},应使用本地 Web Serial API`);
callback([null, { code: 0, time: 0 }]);
return;
}
const shell = this.#shellMicroPython_.getItem(socket.id);
const [error, result] = await to(shell.burn(config));
error && Debug.error(error);
@@ -180,6 +186,12 @@ export default class Socket {
});
socket.on('micropython.upload', async (config, callback) => {
// 检查端口是否是有效的系统设备路径(而非前端内部标识符如 serial1
if (config.port && !config.port.startsWith('/dev/') && !config.port.includes(':')) {
console.log(`[MicroPython] 忽略无效端口请求: ${config.port},应使用本地 Web Serial API`);
callback([null, { code: 0, time: 0 }]);
return;
}
const shell = this.#shellMicroPython_.getItem(socket.id);
let { filePath = '', libraries = {} } = config;
filePath = MString.tpl(filePath, {
@@ -250,8 +262,16 @@ export default class Socket {
error2 && Debug.error(error2);
let [error3,] = await to(fsExtra.outputFile(config.path.code, config.code));
error3 && Debug.error(error3);
const [error, result] = await to(shell.upload(config));
error && Debug.error(error);
const [error, result] = await to(shell.compile(config));
if (error) {
Debug.error(error);
callback([error, result]);
return;
}
if (result.code === 0) {
const buildPath = config.path.build;
result.files = await Boards.getFiles(config.key, buildPath);
}
callback([error, result]);
});