Compare commits
13 Commits
c232332d69
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
708bdc6bf0 | ||
|
|
44b02f4781 | ||
|
|
48ce7f35e1 | ||
|
|
9b80cf73d3 | ||
|
|
67a3a3bd78 | ||
|
|
e8de8f19c2 | ||
|
|
7d2076f165 | ||
|
|
10d5254a7e | ||
|
|
a67284afe5 | ||
|
|
4ac522ffc6 | ||
|
|
5456419bb3 | ||
|
|
9bcc49059e | ||
|
|
0c6199d8e4 |
@@ -1,275 +1,302 @@
|
|||||||
goog.loadJs('web', () => {
|
goog.loadJs('web', () => {
|
||||||
|
|
||||||
goog.require('layui');
|
goog.require('layui');
|
||||||
goog.require('dayjs.duration');
|
goog.require('dayjs.duration');
|
||||||
goog.require('Mixly.Boards');
|
goog.require('Mixly.Boards');
|
||||||
goog.require('Mixly.Debug');
|
goog.require('Mixly.Debug');
|
||||||
goog.require('Mixly.LayerExt');
|
goog.require('Mixly.LayerExt');
|
||||||
goog.require('Mixly.Msg');
|
goog.require('Mixly.Msg');
|
||||||
goog.require('Mixly.Workspace');
|
goog.require('Mixly.Workspace');
|
||||||
goog.require('Mixly.LayerProgress');
|
goog.require('Mixly.LayerProgress');
|
||||||
goog.require('Mixly.WebSocket.Serial');
|
goog.require('Mixly.WebSocket.Serial');
|
||||||
goog.provide('Mixly.WebSocket.ArduShell');
|
goog.require('Mixly.WebCompiler.ArduShell');
|
||||||
|
goog.provide('Mixly.WebSocket.ArduShell');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Boards,
|
Boards,
|
||||||
Debug,
|
Debug,
|
||||||
LayerExt,
|
LayerExt,
|
||||||
Msg,
|
Msg,
|
||||||
Workspace,
|
Workspace,
|
||||||
LayerProgress,
|
LayerProgress,
|
||||||
WebSocket
|
WebSocket,
|
||||||
} = Mixly;
|
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 {
|
static {
|
||||||
this.mixlySocket = null;
|
this.mixlySocket = null;
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
this.shell = null;
|
this.shell = null;
|
||||||
|
|
||||||
this.getSocket = function () {
|
this.getSocket = function () {
|
||||||
return this.socket;
|
return this.socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getMixlySocket = function () {
|
this.getMixlySocket = function () {
|
||||||
return this.mixlySocket;
|
return this.mixlySocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.init = function (mixlySocket) {
|
this.init = function (mixlySocket) {
|
||||||
this.mixlySocket = mixlySocket;
|
this.mixlySocket = mixlySocket;
|
||||||
this.socket = mixlySocket.getSocket();
|
this.socket = mixlySocket.getSocket();
|
||||||
this.shell = new WebSocketArduShell();
|
this.shell = new WebSocketArduShell();
|
||||||
const socket = this.socket;
|
const socket = this.socket;
|
||||||
|
|
||||||
socket.on('arduino.dataEvent', (data) => {
|
// 同时初始化 WebCompiler.ArduShell,用于本地上传
|
||||||
if (data.length > 1000) {
|
const WebCompilerArduShell = getWebCompilerArduShell();
|
||||||
return;
|
if (WebCompilerArduShell && !WebCompilerArduShell.getMixlySocket()) {
|
||||||
|
WebCompilerArduShell.init(mixlySocket);
|
||||||
}
|
}
|
||||||
const { mainStatusBarTabs } = Mixly;
|
|
||||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
|
||||||
statusBarTerminal.addValue(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('arduino.errorEvent', (data) => {
|
socket.on('arduino.dataEvent', (data) => {
|
||||||
const { mainStatusBarTabs } = Mixly;
|
if (data.length > 1000) {
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
mainStatusBarTabs.add('serial', port);
|
const { mainStatusBarTabs } = Mixly;
|
||||||
mainStatusBarTabs.changeTo(port);
|
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||||
const statusBarSerial = mainStatusBarTabs.getStatusBarById(port);
|
statusBarTerminal.addValue(data);
|
||||||
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) {
|
socket.on('arduino.errorEvent', (data) => {
|
||||||
const { mainStatusBarTabs } = Mixly;
|
const { mainStatusBarTabs } = Mixly;
|
||||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||||
mainStatusBarTabs.changeTo('output');
|
try {
|
||||||
let message = '';
|
data = unescape(data.replace(/(_E[0-9A-F]{1}_[0-9A-F]{2}_[0-9A-F]{2})+/gm, '%$1'));
|
||||||
if (code) {
|
data = unescape(data.replace(/\\(u[0-9a-fA-F]{4})/gm, '%$1'));
|
||||||
message = (this.shell.isCompiling() ? Msg.Lang['shell.compileFailed'] : Msg.Lang['shell.uploadFailed']);
|
} catch (error) {
|
||||||
statusBarTerminal.addValue(`\n==${message}==\n`);
|
Debug.error(error);
|
||||||
} 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')
|
if (typeof data === 'string' && (
|
||||||
})==\n`);
|
data.includes('cannot open serial1') ||
|
||||||
|
data.includes('cannot open device "undefined"') ||
|
||||||
|
data.includes('No such file or directory, cannot open')
|
||||||
|
)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
WebSocket.ArduShell = WebSocketArduShell;
|
||||||
#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;
|
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -31,8 +31,8 @@ goog.loadJs('web', () => {
|
|||||||
|
|
||||||
const { SELECTED_BOARD, BOARD } = Config;
|
const { SELECTED_BOARD, BOARD } = Config;
|
||||||
|
|
||||||
// 引入本地 Web.BU 模块,用于 MicroPython 本地上传
|
// WebBU 需要在使用时动态获取,避免模块加载顺序问题
|
||||||
const WebBU = Mixly.Web?.BU;
|
const getWebBU = () => Mixly.Web?.BU;
|
||||||
|
|
||||||
const { Serial } = WebSocket;
|
const { Serial } = WebSocket;
|
||||||
|
|
||||||
@@ -74,6 +74,7 @@ goog.loadJs('web', () => {
|
|||||||
|
|
||||||
this.initBurn = function () {
|
this.initBurn = function () {
|
||||||
// MicroPython 板卡使用本地 Web Serial API 烧录,而非通过服务器
|
// MicroPython 板卡使用本地 Web Serial API 烧录,而非通过服务器
|
||||||
|
const WebBU = getWebBU();
|
||||||
if (SELECTED_BOARD?.language === 'MicroPython' && WebBU) {
|
if (SELECTED_BOARD?.language === 'MicroPython' && WebBU) {
|
||||||
return WebBU.initBurn();
|
return WebBU.initBurn();
|
||||||
}
|
}
|
||||||
@@ -111,6 +112,7 @@ goog.loadJs('web', () => {
|
|||||||
|
|
||||||
this.initUpload = function () {
|
this.initUpload = function () {
|
||||||
// MicroPython 板卡使用本地 Web Serial API 上传,而非通过服务器
|
// MicroPython 板卡使用本地 Web Serial API 上传,而非通过服务器
|
||||||
|
const WebBU = getWebBU();
|
||||||
if (SELECTED_BOARD?.language === 'MicroPython' && WebBU) {
|
if (SELECTED_BOARD?.language === 'MicroPython' && WebBU) {
|
||||||
return WebBU.initUpload();
|
return WebBU.initUpload();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,429 +1,434 @@
|
|||||||
goog.loadJs('web', () => {
|
goog.loadJs('web', () => {
|
||||||
|
|
||||||
goog.require('path');
|
goog.require('path');
|
||||||
goog.require('Mustache');
|
goog.require('Mustache');
|
||||||
goog.require('Mixly.Env');
|
goog.require('Mixly.Env');
|
||||||
goog.require('Mixly.Events');
|
goog.require('Mixly.Events');
|
||||||
goog.require('Mixly.Msg');
|
goog.require('Mixly.Msg');
|
||||||
goog.require('Mixly.Ampy');
|
goog.require('Mixly.Ampy');
|
||||||
goog.require('Mixly.Web');
|
goog.require('Mixly.Web');
|
||||||
goog.provide('Mixly.Web.Ampy');
|
goog.provide('Mixly.Web.Ampy');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
Env,
|
Env,
|
||||||
Events,
|
Events,
|
||||||
Msg,
|
Msg,
|
||||||
Ampy,
|
Ampy,
|
||||||
Web
|
Web
|
||||||
} = Mixly;
|
} = Mixly;
|
||||||
|
|
||||||
|
|
||||||
class AmpyExt extends Ampy {
|
class AmpyExt extends Ampy {
|
||||||
static {
|
static {
|
||||||
this.LS = goog.readFileSync(path.join(Env.templatePath, 'python/ls.py'));
|
this.LS = goog.readFileSync(path.join(Env.templatePath, 'python/ls.py'));
|
||||||
this.LS_RECURSIVE = goog.readFileSync(path.join(Env.templatePath, 'python/ls-recursive.py'));
|
this.LS_RECURSIVE = goog.readFileSync(path.join(Env.templatePath, 'python/ls-recursive.py'));
|
||||||
this.LS_LONG_FORMAT = goog.readFileSync(path.join(Env.templatePath, 'python/ls-long-format.py'));
|
this.LS_LONG_FORMAT = goog.readFileSync(path.join(Env.templatePath, 'python/ls-long-format.py'));
|
||||||
this.MKDIR = goog.readFileSync(path.join(Env.templatePath, 'python/mkdir.py'));
|
this.MKDIR = goog.readFileSync(path.join(Env.templatePath, 'python/mkdir.py'));
|
||||||
this.MKFILE = goog.readFileSync(path.join(Env.templatePath, 'python/mkfile.py'));
|
this.MKFILE = goog.readFileSync(path.join(Env.templatePath, 'python/mkfile.py'));
|
||||||
this.RENAME = goog.readFileSync(path.join(Env.templatePath, 'python/rename.py'));
|
this.RENAME = goog.readFileSync(path.join(Env.templatePath, 'python/rename.py'));
|
||||||
this.RM = goog.readFileSync(path.join(Env.templatePath, 'python/rm.py'));
|
this.RM = goog.readFileSync(path.join(Env.templatePath, 'python/rm.py'));
|
||||||
this.RMDIR = goog.readFileSync(path.join(Env.templatePath, 'python/rmdir.py'));
|
this.RMDIR = goog.readFileSync(path.join(Env.templatePath, 'python/rmdir.py'));
|
||||||
this.GET = goog.readFileSync(path.join(Env.templatePath, 'python/get.py'));
|
this.GET = goog.readFileSync(path.join(Env.templatePath, 'python/get.py'));
|
||||||
this.CWD = goog.readFileSync(path.join(Env.templatePath, 'python/cwd.py'));
|
this.CWD = goog.readFileSync(path.join(Env.templatePath, 'python/cwd.py'));
|
||||||
this.CPDIR = goog.readFileSync(path.join(Env.templatePath, 'python/cpdir.py'));
|
this.CPDIR = goog.readFileSync(path.join(Env.templatePath, 'python/cpdir.py'));
|
||||||
this.CPFILE = goog.readFileSync(path.join(Env.templatePath, 'python/cpfile.py'));
|
this.CPFILE = goog.readFileSync(path.join(Env.templatePath, 'python/cpfile.py'));
|
||||||
}
|
}
|
||||||
|
|
||||||
#device_ = null;
|
#device_ = null;
|
||||||
#receiveTemp_ = [];
|
#receiveTemp_ = [];
|
||||||
#writeBuffer_ = true;
|
#writeBuffer_ = true;
|
||||||
#active_ = false;
|
#active_ = false;
|
||||||
#dataLength_ = 256;
|
#dataLength_ = 256;
|
||||||
#events_ = new Events(['message', 'replaceMessage'])
|
#events_ = new Events(['message', 'replaceMessage'])
|
||||||
constructor(device, writeBuffer = true, dataLength = 256) {
|
constructor(device, writeBuffer = true, dataLength = 256) {
|
||||||
super();
|
super();
|
||||||
this.#device_ = device;
|
this.#device_ = device;
|
||||||
this.#writeBuffer_ = writeBuffer;
|
this.#writeBuffer_ = writeBuffer;
|
||||||
this.#dataLength_ = dataLength;
|
this.#dataLength_ = dataLength;
|
||||||
this.#addEventsListener_();
|
this.#addEventsListener_();
|
||||||
}
|
}
|
||||||
|
|
||||||
#addEventsListener_() {
|
#addEventsListener_() {
|
||||||
this.#device_.bind('onChar', (char) => {
|
this.#device_.bind('onChar', (char) => {
|
||||||
if (['\r', '\n'].includes(char)) {
|
if (['\r', '\n'].includes(char)) {
|
||||||
this.#receiveTemp_.push('');
|
this.#receiveTemp_.push('');
|
||||||
} else {
|
|
||||||
let line = this.#receiveTemp_.pop() ?? '';
|
|
||||||
this.#receiveTemp_.push(line + char);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bind(...args) {
|
|
||||||
return this.#events_.bind(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
message(message) {
|
|
||||||
this.#events_.run('message', message);
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceMessage(lineNumber, message) {
|
|
||||||
this.#events_.run('replaceMessage', lineNumber, message);
|
|
||||||
}
|
|
||||||
|
|
||||||
getProgressMessage(name, percent) {
|
|
||||||
const sended = parseInt(percent * 45);
|
|
||||||
const left = percent === 0 ? '' : Array(sended).fill('=').join('');
|
|
||||||
const right = percent === 100 ? '' : Array(45 - sended).fill('-').join('');
|
|
||||||
return `${name} → |${left}${right}| ${(percent * 100).toFixed(1)}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
isActive() {
|
|
||||||
return this.#active_;
|
|
||||||
}
|
|
||||||
|
|
||||||
async readUntil(ending, withEnding = true, timeout = 5000) {
|
|
||||||
const startTime = Number(new Date());
|
|
||||||
let nowTime = startTime;
|
|
||||||
let readStr = '';
|
|
||||||
while (nowTime - startTime < timeout) {
|
|
||||||
const nowTime = Number(new Date());
|
|
||||||
let len = this.#receiveTemp_.length;
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
const data = this.#receiveTemp_.shift();
|
|
||||||
let index = data.toLowerCase().indexOf(ending);
|
|
||||||
if (index !== -1) {
|
|
||||||
if (withEnding) {
|
|
||||||
index += ending.length;
|
|
||||||
}
|
|
||||||
this.#receiveTemp_.unshift(data.substring(index));
|
|
||||||
readStr += data.substring(0, index);
|
|
||||||
return readStr;
|
|
||||||
} else {
|
} else {
|
||||||
readStr += data;
|
let line = this.#receiveTemp_.pop() ?? '';
|
||||||
if (i !== len - 1) {
|
this.#receiveTemp_.push(line + char);
|
||||||
readStr += '\n';
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bind(...args) {
|
||||||
|
return this.#events_.bind(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
message(message) {
|
||||||
|
this.#events_.run('message', message);
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceMessage(lineNumber, message) {
|
||||||
|
this.#events_.run('replaceMessage', lineNumber, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
getProgressMessage(name, percent) {
|
||||||
|
const sended = parseInt(percent * 45);
|
||||||
|
const left = percent === 0 ? '' : Array(sended).fill('=').join('');
|
||||||
|
const right = percent === 100 ? '' : Array(45 - sended).fill('-').join('');
|
||||||
|
return `${name} → |${left}${right}| ${(percent * 100).toFixed(1)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive() {
|
||||||
|
return this.#active_;
|
||||||
|
}
|
||||||
|
|
||||||
|
async readUntil(ending, withEnding = true, timeout = 5000) {
|
||||||
|
const startTime = Number(new Date());
|
||||||
|
let nowTime = startTime;
|
||||||
|
let readStr = '';
|
||||||
|
while (nowTime - startTime < timeout) {
|
||||||
|
const nowTime = Number(new Date());
|
||||||
|
let len = this.#receiveTemp_.length;
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const data = this.#receiveTemp_.shift();
|
||||||
|
let index = data.toLowerCase().indexOf(ending);
|
||||||
|
if (index !== -1) {
|
||||||
|
if (withEnding) {
|
||||||
|
index += ending.length;
|
||||||
|
}
|
||||||
|
this.#receiveTemp_.unshift(data.substring(index));
|
||||||
|
readStr += data.substring(0, index);
|
||||||
|
return readStr;
|
||||||
|
} else {
|
||||||
|
readStr += data;
|
||||||
|
if (i !== len - 1) {
|
||||||
|
readStr += '\n';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (nowTime - startTime >= timeout) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (!this.isActive()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.dataReadInterrupt']);
|
||||||
|
}
|
||||||
|
await this.#device_.sleep(100);
|
||||||
}
|
}
|
||||||
if (nowTime - startTime >= timeout) {
|
}
|
||||||
|
|
||||||
|
async interrupt(timeout = 1000) {
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
// 中断两次
|
||||||
|
await this.#device_.sendBuffer([0x0D, 0x03]);
|
||||||
|
await this.#device_.sleep(100);
|
||||||
|
await this.#device_.sendBuffer([0x03]);
|
||||||
|
await this.#device_.sleep(100);
|
||||||
|
if (await this.readUntil('>>>', true, timeout)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async enterRawREPL(timeout = 1000) {
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
await this.#device_.sendBuffer([0x01]);
|
||||||
|
await this.#device_.sleep(100);
|
||||||
|
if (await this.readUntil('raw repl; ctrl-b to exit', true, timeout)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async exitRawREPL(timeout = 5000) {
|
||||||
|
await this.#device_.sendBuffer([0x02]);
|
||||||
|
await this.#device_.sleep(100);
|
||||||
|
let succeed = false;
|
||||||
|
if (await this.readUntil('>>>', true, timeout)) {
|
||||||
|
succeed = true;
|
||||||
|
}
|
||||||
|
return succeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async exitREPL(timeout = 5000) {
|
||||||
|
await this.#device_.sendBuffer([0x04]);
|
||||||
|
await this.#device_.sleep(100);
|
||||||
|
let succeed = false;
|
||||||
|
if (await this.readUntil('soft reboot', false, timeout)) {
|
||||||
|
succeed = true;
|
||||||
|
}
|
||||||
|
return succeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async exec(str, timeout = 5000) {
|
||||||
|
if (this.#writeBuffer_) {
|
||||||
|
const buffer = this.#device_.encode(str);
|
||||||
|
const len = Math.ceil(buffer.length / this.#dataLength_);
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
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 / 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.#device_.sendBuffer([0x04]);
|
||||||
|
return await this.follow(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
async follow(timeout = 1000) {
|
||||||
|
let data = await this.readUntil('\x04', true, timeout);
|
||||||
|
if (data.length < 1) {
|
||||||
|
throw new Error(Msg.Lang['ampy.waitingFirstEOFTimeout']);
|
||||||
|
}
|
||||||
|
let start = data.toLowerCase().lastIndexOf('ok');
|
||||||
|
if (start === -1) {
|
||||||
|
start = 0;
|
||||||
|
} else {
|
||||||
|
start += 2;
|
||||||
|
}
|
||||||
|
data = data.substring(start, data.length - 1);
|
||||||
|
let dataError = await this.readUntil('\x04', true, timeout);
|
||||||
|
if (dataError.length < 1) {
|
||||||
|
throw new Error(Msg.Lang['ampy.secondEOFTimeout']);
|
||||||
|
}
|
||||||
|
dataError = dataError.substring(0, dataError.length - 1);
|
||||||
|
return { data, dataError };
|
||||||
|
}
|
||||||
|
|
||||||
|
async enter() {
|
||||||
|
if (this.isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#active_ = true;
|
||||||
|
await this.#device_.open(115200);
|
||||||
|
await this.#device_.sleep(500);
|
||||||
|
await this.#device_.sendBuffer([0x02]);
|
||||||
|
if (!await this.interrupt()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.interruptFailed']);
|
||||||
|
}
|
||||||
|
if (!await this.enterRawREPL()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.enterRawREPLFailed']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exit() {
|
||||||
|
if (!this.isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!await this.exitRawREPL()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.exitRawREPLFailed']);
|
||||||
|
}
|
||||||
|
// 发送 Ctrl+D 触发软复位,让设备执行上传的 main.py
|
||||||
|
if (!await this.exitREPL()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.exitREPLFailed']);
|
||||||
|
}
|
||||||
|
await this.#device_.close();
|
||||||
|
this.#active_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(filename, encoding = 'utf8', timeout = 5000) {
|
||||||
|
if (!this.isActive()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
||||||
|
}
|
||||||
|
const code = Mustache.render(AmpyExt.GET, {
|
||||||
|
path: filename
|
||||||
|
});
|
||||||
|
const { data, dataError } = await this.exec(code, timeout);
|
||||||
|
if (dataError) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
if (encoding === 'utf8') {
|
||||||
|
return this.#device_.decode(this.unhexlify(data));
|
||||||
|
} else {
|
||||||
|
return this.unhexlify(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async put(filename, data, timeout = 5000) {
|
||||||
if (!this.isActive()) {
|
if (!this.isActive()) {
|
||||||
throw new Error(Msg.Lang['ampy.dataReadInterrupt']);
|
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
||||||
}
|
}
|
||||||
await this.#device_.sleep(100);
|
this.message(`Writing ${filename}...\n`);
|
||||||
}
|
this.message(this.getProgressMessage('', 0));
|
||||||
}
|
await this.exec(`file = open('${filename}', 'wb')`, timeout);
|
||||||
|
let buffer = null;
|
||||||
async interrupt(timeout = 1000) {
|
if (data.constructor === String) {
|
||||||
for (let i = 0; i < 5; i++) {
|
buffer = this.#device_.encode(data);
|
||||||
// 中断两次
|
} else if (data.constructor === ArrayBuffer) {
|
||||||
await this.#device_.sendBuffer([0x0D, 0x03]);
|
buffer = new Uint8Array(data);
|
||||||
await this.#device_.sleep(100);
|
} else {
|
||||||
await this.#device_.sendBuffer([0x03]);
|
buffer = data;
|
||||||
await this.#device_.sleep(100);
|
|
||||||
if (await this.readUntil('>>>', true, timeout)) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
const len = Math.ceil(buffer.length / 64);
|
||||||
return false;
|
if (!len) {
|
||||||
}
|
this.replaceMessage(-1, this.getProgressMessage('', 1));
|
||||||
|
|
||||||
async enterRawREPL(timeout = 1000) {
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
await this.#device_.sendBuffer([0x01]);
|
|
||||||
await this.#device_.sleep(100);
|
|
||||||
if (await this.readUntil('raw repl; ctrl-b to exit', true, timeout)) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
let sendedLength = 0;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async exitRawREPL(timeout = 5000) {
|
|
||||||
await this.#device_.sendBuffer([0x02]);
|
|
||||||
await this.#device_.sleep(100);
|
|
||||||
let succeed = false;
|
|
||||||
if (await this.readUntil('>>>', true, timeout)) {
|
|
||||||
succeed = true;
|
|
||||||
}
|
|
||||||
return succeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
async exitREPL(timeout = 5000) {
|
|
||||||
await this.#device_.sendBuffer([0x04]);
|
|
||||||
await this.#device_.sleep(100);
|
|
||||||
let succeed = false;
|
|
||||||
if (await this.readUntil('soft reboot', false, timeout)) {
|
|
||||||
succeed = true;
|
|
||||||
}
|
|
||||||
return succeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
async exec(str, timeout = 5000) {
|
|
||||||
if (this.#writeBuffer_) {
|
|
||||||
const buffer = this.#device_.encode(str);
|
|
||||||
const len = Math.ceil(buffer.length / this.#dataLength_);
|
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
const start = i * this.#dataLength_;
|
const writeBuffer = buffer.slice(i * 64, Math.min((i + 1) * 64, buffer.length));
|
||||||
const end = Math.min((i + 1) * this.#dataLength_, buffer.length);
|
sendedLength += writeBuffer.length;
|
||||||
const writeBuffer = buffer.slice(start, end);
|
const percent = sendedLength / buffer.length;
|
||||||
await this.#device_.sendBuffer(writeBuffer);
|
this.replaceMessage(-1, this.getProgressMessage('', percent));
|
||||||
await this.#device_.sleep(10);
|
let writeStr = '';
|
||||||
}
|
for (let num of writeBuffer) {
|
||||||
} else {
|
let numStr = num.toString(16);
|
||||||
for (let i = 0; i < str.length / this.#dataLength_; i++) {
|
if (numStr.length === 1) {
|
||||||
const start = i * this.#dataLength_;
|
numStr = '0' + numStr;
|
||||||
const end = Math.min((i + 1) * this.#dataLength_, str.length);
|
}
|
||||||
let data = str.substring(start, end);
|
writeStr += '\\x' + numStr;
|
||||||
await this.#device_.sendString(data);
|
|
||||||
await this.#device_.sleep(10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await this.#device_.sendBuffer([0x04]);
|
|
||||||
return await this.follow(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
async follow(timeout = 1000) {
|
|
||||||
let data = await this.readUntil('\x04', true, timeout);
|
|
||||||
if (data.length < 1) {
|
|
||||||
throw new Error(Msg.Lang['ampy.waitingFirstEOFTimeout']);
|
|
||||||
}
|
|
||||||
let start = data.toLowerCase().lastIndexOf('ok');
|
|
||||||
if (start === -1) {
|
|
||||||
start = 0;
|
|
||||||
} else {
|
|
||||||
start += 2;
|
|
||||||
}
|
|
||||||
data = data.substring(start, data.length - 1);
|
|
||||||
let dataError = await this.readUntil('\x04', true, timeout);
|
|
||||||
if (dataError.length < 1) {
|
|
||||||
throw new Error(Msg.Lang['ampy.secondEOFTimeout']);
|
|
||||||
}
|
|
||||||
dataError = dataError.substring(0, dataError.length - 1);
|
|
||||||
return { data, dataError };
|
|
||||||
}
|
|
||||||
|
|
||||||
async enter() {
|
|
||||||
if (this.isActive()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.#active_ = true;
|
|
||||||
await this.#device_.open(115200);
|
|
||||||
await this.#device_.sleep(500);
|
|
||||||
await this.#device_.sendBuffer([0x02]);
|
|
||||||
if (!await this.interrupt()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.interruptFailed']);
|
|
||||||
}
|
|
||||||
if (!await this.enterRawREPL()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.enterRawREPLFailed']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async exit() {
|
|
||||||
if (!this.isActive()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!await this.exitRawREPL()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.exitRawREPLFailed']);
|
|
||||||
}
|
|
||||||
/*if (!await this.exitREPL()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.exitREPLFailed']);
|
|
||||||
}*/
|
|
||||||
await this.#device_.close();
|
|
||||||
this.#active_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async get(filename, encoding = 'utf8', timeout = 5000) {
|
|
||||||
if (!this.isActive()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
|
||||||
}
|
|
||||||
const code = Mustache.render(AmpyExt.GET, {
|
|
||||||
path: filename
|
|
||||||
});
|
|
||||||
const { data, dataError } = await this.exec(code, timeout);
|
|
||||||
if (dataError) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
if (encoding === 'utf8') {
|
|
||||||
return this.#device_.decode(this.unhexlify(data));
|
|
||||||
} else {
|
|
||||||
return this.unhexlify(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async put(filename, data, timeout = 5000) {
|
|
||||||
if (!this.isActive()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
|
||||||
}
|
|
||||||
this.message(`Writing ${filename}...\n`);
|
|
||||||
this.message(this.getProgressMessage('', 0));
|
|
||||||
await this.exec(`file = open('${filename}', 'wb')`, timeout);
|
|
||||||
let buffer = null;
|
|
||||||
if (data.constructor === String) {
|
|
||||||
buffer = this.#device_.encode(data);
|
|
||||||
} else if (data.constructor === ArrayBuffer) {
|
|
||||||
buffer = new Uint8Array(data);
|
|
||||||
} else {
|
|
||||||
buffer = data;
|
|
||||||
}
|
|
||||||
const len = Math.ceil(buffer.length / 64);
|
|
||||||
if (!len) {
|
|
||||||
this.replaceMessage(-1, this.getProgressMessage('', 1));
|
|
||||||
}
|
|
||||||
let sendedLength = 0;
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
const writeBuffer = buffer.slice(i * 64, Math.min((i + 1) * 64, buffer.length));
|
|
||||||
sendedLength += writeBuffer.length;
|
|
||||||
const percent = sendedLength / buffer.length;
|
|
||||||
this.replaceMessage(-1, this.getProgressMessage('', percent));
|
|
||||||
let writeStr = '';
|
|
||||||
for (let num of writeBuffer) {
|
|
||||||
let numStr = num.toString(16);
|
|
||||||
if (numStr.length === 1) {
|
|
||||||
numStr = '0' + numStr;
|
|
||||||
}
|
}
|
||||||
writeStr += '\\x' + numStr;
|
await this.exec(`file.write(b'${writeStr}')`, timeout);
|
||||||
}
|
}
|
||||||
await this.exec(`file.write(b'${writeStr}')`, timeout);
|
await this.exec('file.close()', timeout);
|
||||||
|
this.message('\n');
|
||||||
}
|
}
|
||||||
await this.exec('file.close()', timeout);
|
|
||||||
this.message('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
async ls(directory = '/', longFormat = true, recursive = false, timeout = 5000) {
|
async ls(directory = '/', longFormat = true, recursive = false, timeout = 5000) {
|
||||||
if (!this.isActive()) {
|
if (!this.isActive()) {
|
||||||
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
||||||
|
}
|
||||||
|
let code = '';
|
||||||
|
if (longFormat) {
|
||||||
|
code = Mustache.render(AmpyExt.LS_LONG_FORMAT, {
|
||||||
|
path: directory
|
||||||
|
});
|
||||||
|
} else if (recursive) {
|
||||||
|
code = Mustache.render(AmpyExt.LS_RECURSIVE, {
|
||||||
|
path: directory
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
code = Mustache.render(AmpyExt.LS, {
|
||||||
|
path: directory
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { data, dataError } = await this.exec(code, timeout);
|
||||||
|
if (dataError) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return JSON.parse(data.replaceAll('\'', '\"'));
|
||||||
}
|
}
|
||||||
let code = '';
|
|
||||||
if (longFormat) {
|
async mkdir(directory, timeout = 5000) {
|
||||||
code = Mustache.render(AmpyExt.LS_LONG_FORMAT, {
|
if (!this.isActive()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
||||||
|
}
|
||||||
|
const code = Mustache.render(AmpyExt.MKDIR, {
|
||||||
path: directory
|
path: directory
|
||||||
});
|
});
|
||||||
} else if (recursive) {
|
const { dataError } = await this.exec(code, timeout);
|
||||||
code = Mustache.render(AmpyExt.LS_RECURSIVE, {
|
return !dataError;
|
||||||
|
}
|
||||||
|
|
||||||
|
async mkfile(file, timeout = 5000) {
|
||||||
|
if (!this.isActive()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
||||||
|
}
|
||||||
|
const code = Mustache.render(AmpyExt.MKFILE, {
|
||||||
|
path: file
|
||||||
|
});
|
||||||
|
const { dataError } = await this.exec(code, timeout);
|
||||||
|
return !dataError;
|
||||||
|
}
|
||||||
|
|
||||||
|
async rename(oldname, newname, timeout = 5000) {
|
||||||
|
if (!this.isActive()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
||||||
|
}
|
||||||
|
const code = Mustache.render(AmpyExt.RENAME, {
|
||||||
|
oldPath: oldname,
|
||||||
|
newPath: newname
|
||||||
|
});
|
||||||
|
const { dataError } = await this.exec(code, timeout);
|
||||||
|
return !dataError;
|
||||||
|
}
|
||||||
|
|
||||||
|
async rm(filename, timeout = 5000) {
|
||||||
|
if (!this.isActive()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
||||||
|
}
|
||||||
|
const code = Mustache.render(AmpyExt.RM, {
|
||||||
|
path: filename
|
||||||
|
});
|
||||||
|
await this.exec(code);
|
||||||
|
const { dataError } = await this.exec(code, timeout);
|
||||||
|
return !dataError;
|
||||||
|
}
|
||||||
|
|
||||||
|
async rmdir(directory, timeout = 5000) {
|
||||||
|
if (!this.isActive()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
||||||
|
}
|
||||||
|
const code = Mustache.render(AmpyExt.RMDIR, {
|
||||||
path: directory
|
path: directory
|
||||||
});
|
});
|
||||||
} else {
|
const { dataError } = await this.exec(code, timeout);
|
||||||
code = Mustache.render(AmpyExt.LS, {
|
return !dataError;
|
||||||
path: directory
|
}
|
||||||
|
|
||||||
|
async cpdir(oldname, newname, timeout = 5000) {
|
||||||
|
if (!this.isActive()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
||||||
|
}
|
||||||
|
const code = Mustache.render(AmpyExt.CPDIR, {
|
||||||
|
oldPath: oldname,
|
||||||
|
newPath: newname
|
||||||
});
|
});
|
||||||
|
const { data, dataError } = await this.exec(code, timeout);
|
||||||
|
console.log(data, dataError)
|
||||||
|
return !dataError;
|
||||||
}
|
}
|
||||||
const { data, dataError } = await this.exec(code, timeout);
|
|
||||||
if (dataError) {
|
async cpfile(oldname, newname, timeout = 5000) {
|
||||||
return [];
|
if (!this.isActive()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
||||||
|
}
|
||||||
|
const code = Mustache.render(AmpyExt.CPFILE, {
|
||||||
|
oldPath: oldname,
|
||||||
|
newPath: newname
|
||||||
|
});
|
||||||
|
const { dataError } = await this.exec(code, timeout);
|
||||||
|
return !dataError;
|
||||||
|
}
|
||||||
|
|
||||||
|
async cwd(timeout = 5000) {
|
||||||
|
if (!this.isActive()) {
|
||||||
|
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
||||||
|
}
|
||||||
|
const code = Mustache.render(AmpyExt.CWD, {});
|
||||||
|
const { data, dataError } = await this.exec(code, timeout);
|
||||||
|
if (dataError) {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDevice() {
|
||||||
|
return this.#device_;
|
||||||
|
}
|
||||||
|
|
||||||
|
async dispose() {
|
||||||
|
this.#active_ = false;
|
||||||
|
if (this.#device_) {
|
||||||
|
await this.#device_.dispose();
|
||||||
|
this.#device_ = null;
|
||||||
|
}
|
||||||
|
if (this.#events_) {
|
||||||
|
this.#events_.reset();
|
||||||
|
this.#events_ = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return JSON.parse(data.replaceAll('\'', '\"'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async mkdir(directory, timeout = 5000) {
|
Web.Ampy = AmpyExt;
|
||||||
if (!this.isActive()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
|
||||||
}
|
|
||||||
const code = Mustache.render(AmpyExt.MKDIR, {
|
|
||||||
path: directory
|
|
||||||
});
|
|
||||||
const { dataError } = await this.exec(code, timeout);
|
|
||||||
return !dataError;
|
|
||||||
}
|
|
||||||
|
|
||||||
async mkfile(file, timeout = 5000) {
|
|
||||||
if (!this.isActive()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
|
||||||
}
|
|
||||||
const code = Mustache.render(AmpyExt.MKFILE, {
|
|
||||||
path: file
|
|
||||||
});
|
|
||||||
const { dataError } = await this.exec(code, timeout);
|
|
||||||
return !dataError;
|
|
||||||
}
|
|
||||||
|
|
||||||
async rename(oldname, newname, timeout = 5000) {
|
|
||||||
if (!this.isActive()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
|
||||||
}
|
|
||||||
const code = Mustache.render(AmpyExt.RENAME, {
|
|
||||||
oldPath: oldname,
|
|
||||||
newPath: newname
|
|
||||||
});
|
|
||||||
const { dataError } = await this.exec(code, timeout);
|
|
||||||
return !dataError;
|
|
||||||
}
|
|
||||||
|
|
||||||
async rm(filename, timeout = 5000) {
|
|
||||||
if (!this.isActive()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
|
||||||
}
|
|
||||||
const code = Mustache.render(AmpyExt.RM, {
|
|
||||||
path: filename
|
|
||||||
});
|
|
||||||
await this.exec(code);
|
|
||||||
const { dataError } = await this.exec(code, timeout);
|
|
||||||
return !dataError;
|
|
||||||
}
|
|
||||||
|
|
||||||
async rmdir(directory, timeout = 5000) {
|
|
||||||
if (!this.isActive()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
|
||||||
}
|
|
||||||
const code = Mustache.render(AmpyExt.RMDIR, {
|
|
||||||
path: directory
|
|
||||||
});
|
|
||||||
const { dataError } = await this.exec(code, timeout);
|
|
||||||
return !dataError;
|
|
||||||
}
|
|
||||||
|
|
||||||
async cpdir(oldname, newname, timeout = 5000) {
|
|
||||||
if (!this.isActive()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
|
||||||
}
|
|
||||||
const code = Mustache.render(AmpyExt.CPDIR, {
|
|
||||||
oldPath: oldname,
|
|
||||||
newPath: newname
|
|
||||||
});
|
|
||||||
const { data, dataError } = await this.exec(code, timeout);
|
|
||||||
console.log(data, dataError)
|
|
||||||
return !dataError;
|
|
||||||
}
|
|
||||||
|
|
||||||
async cpfile(oldname, newname, timeout = 5000) {
|
|
||||||
if (!this.isActive()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
|
||||||
}
|
|
||||||
const code = Mustache.render(AmpyExt.CPFILE, {
|
|
||||||
oldPath: oldname,
|
|
||||||
newPath: newname
|
|
||||||
});
|
|
||||||
const { dataError } = await this.exec(code, timeout);
|
|
||||||
return !dataError;
|
|
||||||
}
|
|
||||||
|
|
||||||
async cwd(timeout = 5000) {
|
|
||||||
if (!this.isActive()) {
|
|
||||||
throw new Error(Msg.Lang['ampy.portIsNotOpen']);
|
|
||||||
}
|
|
||||||
const code = Mustache.render(AmpyExt.CWD, {});
|
|
||||||
const { data, dataError } = await this.exec(code, timeout);
|
|
||||||
if (dataError) {
|
|
||||||
return '/';
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
getDevice() {
|
|
||||||
return this.#device_;
|
|
||||||
}
|
|
||||||
|
|
||||||
async dispose() {
|
|
||||||
this.#active_ = false;
|
|
||||||
await this.#device_.dispose();
|
|
||||||
this.#device_ = null;
|
|
||||||
this.#events_.reset();
|
|
||||||
this.#events_ = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Web.Ampy = AmpyExt;
|
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -11,38 +11,60 @@ export default class ShellArduino extends Shell {
|
|||||||
async compile(config) {
|
async compile(config) {
|
||||||
let arduino = _.merge({}, ARDUINO);
|
let arduino = _.merge({}, ARDUINO);
|
||||||
arduino = _.merge(arduino, config);
|
arduino = _.merge(arduino, config);
|
||||||
const command = [
|
|
||||||
`"${arduino.path.cli}"`,
|
// 构建参数数组,避免 shell 解析引号问题
|
||||||
|
const args = [
|
||||||
'compile',
|
'compile',
|
||||||
'-b', arduino.key,
|
'-b', arduino.key,
|
||||||
'--config-file', `"${arduino.path.config}"`,
|
'--config-file', arduino.path.config,
|
||||||
'--verbose',
|
'--verbose'
|
||||||
'--libraries', `"${arduino.path.libraries.join('","')}"`,
|
];
|
||||||
'--build-path', `"${arduino.path.build}"`,
|
|
||||||
'--output-dir', `"${arduino.path.output}"`,
|
// 为每个 library 路径添加参数
|
||||||
`"${arduino.path.code}"`,
|
// 注意:config.json 中的路径是指向包含多个库的集合目录,所以必须用 --libraries
|
||||||
|
// 如果用 --library,arduino-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'
|
'--no-color'
|
||||||
].join(' ');
|
);
|
||||||
return this.execUntilClosed(command, { maxBuffer: 4096 * 1000000 });
|
|
||||||
|
return this.execFileUntilClosed(arduino.path.cli, args, { maxBuffer: 4096 * 1000000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
async upload(config) {
|
async upload(config) {
|
||||||
let arduino = _.merge({}, ARDUINO);
|
let arduino = _.merge({}, ARDUINO);
|
||||||
arduino = _.merge(arduino, config);
|
arduino = _.merge(arduino, config);
|
||||||
const command = [
|
|
||||||
`"${arduino.path.cli}"`,
|
// 构建参数数组,避免 shell 解析引号问题
|
||||||
|
const args = [
|
||||||
'compile',
|
'compile',
|
||||||
'--upload',
|
'--upload',
|
||||||
'-p', arduino.port,
|
'-p', arduino.port,
|
||||||
'-b', arduino.key,
|
'-b', arduino.key,
|
||||||
'--config-file', `"${arduino.path.config}"`,
|
'--config-file', arduino.path.config,
|
||||||
'--verbose',
|
'--verbose'
|
||||||
'--libraries', `"${arduino.path.libraries.join('","')}"`,
|
];
|
||||||
'--build-path', `"${arduino.path.build}"`,
|
|
||||||
'--output-dir', `"${arduino.path.output}"`,
|
// 为每个 library 路径添加参数
|
||||||
`"${arduino.path.code}"`,
|
// 注意:config.json 中的路径是指向包含多个库的集合目录,所以必须用 --libraries
|
||||||
|
// 如果用 --library,arduino-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'
|
'--no-color'
|
||||||
].join(' ');
|
);
|
||||||
return this.execUntilClosed(command, { maxBuffer: 4096 * 1000000 });
|
|
||||||
|
return this.execFileUntilClosed(arduino.path.cli, args, { maxBuffer: 4096 * 1000000 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -173,6 +173,12 @@ export default class Socket {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('micropython.burn', async (config, callback) => {
|
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 shell = this.#shellMicroPython_.getItem(socket.id);
|
||||||
const [error, result] = await to(shell.burn(config));
|
const [error, result] = await to(shell.burn(config));
|
||||||
error && Debug.error(error);
|
error && Debug.error(error);
|
||||||
@@ -180,6 +186,12 @@ export default class Socket {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('micropython.upload', async (config, callback) => {
|
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);
|
const shell = this.#shellMicroPython_.getItem(socket.id);
|
||||||
let { filePath = '', libraries = {} } = config;
|
let { filePath = '', libraries = {} } = config;
|
||||||
filePath = MString.tpl(filePath, {
|
filePath = MString.tpl(filePath, {
|
||||||
@@ -250,8 +262,16 @@ export default class Socket {
|
|||||||
error2 && Debug.error(error2);
|
error2 && Debug.error(error2);
|
||||||
let [error3,] = await to(fsExtra.outputFile(config.path.code, config.code));
|
let [error3,] = await to(fsExtra.outputFile(config.path.code, config.code));
|
||||||
error3 && Debug.error(error3);
|
error3 && Debug.error(error3);
|
||||||
const [error, result] = await to(shell.upload(config));
|
const [error, result] = await to(shell.compile(config));
|
||||||
error && Debug.error(error);
|
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]);
|
callback([error, result]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user