Files
mixly3/common/modules/mixly-modules/web/burn-upload.js

689 lines
23 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
goog.loadJs('web', () => {
goog.require('ESPTool');
goog.require('AdafruitESPTool');
goog.require('CryptoJS');
goog.require('AvrUploader');
goog.require('Mixly.Env');
goog.require('Mixly.LayerExt');
goog.require('Mixly.Config');
goog.require('Mixly.MFile');
goog.require('Mixly.Boards');
goog.require('Mixly.Msg');
goog.require('Mixly.Workspace');
goog.require('Mixly.Debug');
goog.require('Mixly.Web.Serial');
goog.require('Mixly.Web.USB');
goog.require('Mixly.Web.Ampy');
goog.provide('Mixly.Web.BU');
const {
Env,
Web,
LayerExt,
Config,
MFile,
Boards,
Msg,
Workspace,
Debug
} = Mixly;
const {
Serial,
Esptool,
BU,
USB,
Ampy
} = Web;
const { BOARD, SELECTED_BOARD } = Config;
const {
ESPLoader,
Transport
} = ESPTool;
BU.uploading = false;
BU.burning = false;
const BAUD = goog.platform() === 'darwin' ? 460800 : 921600;
BU.requestPort = () => {
if (SELECTED_BOARD.web.com === 'usb') {
USB.requestPort();
} else {
Serial.requestPort();
}
}
const readBinFile = (path, offset) => {
return new Promise((resolve, reject) => {
fetch(path)
.then((response) => {
return response.blob();
})
.then((blob) => {
const reader = new FileReader();
reader.onload = function (event) {
resolve({
address: parseInt(offset),
data: event.target.result
});
};
reader.onerror = function (error) {
throw(error);
}
reader.readAsBinaryString(blob);
})
.catch((error) => {
reject(error);
});
});
}
const readBinFileAsArrayBuffer = (path, offset) => {
return new Promise((resolve, reject) => {
fetch(path)
.then((response) => {
return response.blob();
})
.then((blob) => {
const reader = new FileReader();
reader.onload = function (event) {
resolve({
address: parseInt(offset),
data: event.target.result
});
};
reader.onerror = function (error) {
throw(error);
}
reader.readAsArrayBuffer(blob);
})
.catch((error) => {
reject(error);
});
});
}
BU.initBurn = () => {
if (SELECTED_BOARD.web.com === 'usb') {
BU.burnByUSB();
} else {
const boardKey = Boards.getSelectedBoardKey();
if (boardKey.indexOf('micropython:esp32s2') !== -1) {
BU.burnWithAdafruitEsptool();
} else {
BU.burnWithEsptool();
}
}
}
BU.burnByUSB = () => {
const portName = 'web-usb';
Serial.connect(portName, 115200, async (port) => {
if (!port) {
return;
}
let portObj = Serial.portsOperator[portName];
const { toolConfig, serialport } = portObj;
const prevBaud = toolConfig.baudRates;
if (prevBaud !== 115200) {
toolConfig.baudRates = 115200;
await serialport.setBaudRate(toolConfig.baudRates);
}
const { web } = SELECTED_BOARD;
const { burn } = web;
const hexStr = goog.get(path.join(Env.boardDirPath, burn.filePath));
const hex2Blob = new Blob([ hexStr ], { type: 'text/plain' });
const buffer = await hex2Blob.arrayBuffer();
if (!buffer) {
layer.msg(Msg.Lang['shell.bin.readFailed'], { time: 1000 });
return;
}
BU.burning = true;
BU.uploading = false;
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.setValue(Msg.Lang['shell.burning'] + '...\n');
mainStatusBarTabs.show();
mainStatusBarTabs.changeTo('output');
const layerNum = layer.open({
type: 1,
title: Msg.Lang['shell.burning'] + '...',
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").hide();
let prevPercent = 0;
USB.DAPLink.on(DAPjs.DAPLink.EVENT_PROGRESS, progress => {
const nowPercent = Math.floor(progress * 100);
if (nowPercent > prevPercent) {
prevPercent = nowPercent;
} else {
return;
}
const nowProgressLen = Math.floor(nowPercent / 2);
const leftStr = new Array(nowProgressLen).fill('=').join('');
const rightStr = (new Array(50 - nowProgressLen).fill('-')).join('');
statusBarTerminal.addValue(`[${leftStr}${rightStr}] ${nowPercent}%\n`);
});
USB.flash(buffer)
.then(() => {
layer.close(index);
layer.msg(Msg.Lang['shell.burnSucc'], { time: 1000 });
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnSucc']}==\n`);
})
.catch((error) => {
console.log(error);
layer.close(index);
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnFailed']}==\n`);
})
.finally(async () => {
BU.burning = false;
BU.uploading = false;
if (toolConfig.baudRates !== prevBaud) {
toolConfig.baudRates = prevBaud;
await serialport.setBaudRate(prevBaud);
}
USB.DAPLink.removeAllListeners(DAPjs.DAPLink.EVENT_PROGRESS);
});
},
end: function () {
$("#mixly-loader-btn").css('display', 'inline-block');
$('#mixly-loader-div').css('display', 'none');
$("#layui-layer-shade" + layerNum).remove();
}
});
});
}
BU.burnWithEsptool = async () => {
const { web } = SELECTED_BOARD;
const { burn } = web;
const { mainStatusBarTabs } = Mixly;
const portName = Serial.getSelectedPortName();
if (!portName) {
/*layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});*/
BU.requestPort();
return;
}
const statusBarSerial = mainStatusBarTabs.getStatusBarById(portName);
if (statusBarSerial) {
await statusBarSerial.close();
}
const port = Serial.getPort(portName);
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.setValue(Msg.Lang['shell.burning'] + '...\n');
mainStatusBarTabs.show();
mainStatusBarTabs.changeTo('output');
let esploader = null;
let transport = null;
try {
transport = new Transport(port, false);
esploader = new ESPLoader({
transport,
baudrate: BAUD,
terminal: {
clean() {
statusBarTerminal.setValue('');
},
writeLine(data) {
statusBarTerminal.addValue(data + '\n');
},
write(data) {
statusBarTerminal.addValue(data);
}
}
});
let chip = await esploader.main();
} catch (error) {
console.log(error);
statusBarTerminal.addValue(`\n${error.toString()}\n`);
await transport.disconnect();
return;
}
statusBarTerminal.addValue(Msg.Lang['shell.bin.reading'] + "...");
if (typeof burn.binFile !== 'object') {
statusBarTerminal.addValue(" Failed!\n" + Msg.Lang['shell.bin.readFailed'] + "\n");
await transport.disconnect();
return;
}
const { binFile } = burn;
let firmwarePromise = [];
statusBarTerminal.addValue("\n");
for (let i of binFile) {
if (i.path && i.offset) {
let absolutePath = path.join(Env.boardDirPath, i.path);
// statusBarTerminal.addValue(`${Msg.Lang['读取固件'] + ' '
// + Msg.Lang['路径']}:${absolutePath}, ${Msg.Lang['偏移']}:${i.offset}\n`);
firmwarePromise.push(readBinFile(absolutePath, i.offset));
}
}
let data = null;
try {
data = await Promise.all(firmwarePromise);
} catch (error) {
statusBarTerminal.addValue("Failed!\n" + Msg.Lang['shell.bin.readFailed'] + "\n");
statusBarTerminal.addValue("\n" + e + "\n", true);
await transport.disconnect();
return;
}
statusBarTerminal.addValue("Done!\n");
BU.burning = true;
BU.uploading = false;
const flashOptions = {
fileArray: data,
flashSize: 'keep',
eraseAll: false,
compress: true,
calculateMD5Hash: (image) => CryptoJS.MD5(CryptoJS.enc.Latin1.parse(image))
};
const layerNum = layer.open({
type: 1,
title: Msg.Lang['shell.burning'] + '...',
content: $('#mixly-loader-div'),
shade: LayerExt.SHADE_NAV,
resize: false,
closeBtn: 0,
success: async function (layero, index) {
let cancel = false;
$("#mixly-loader-btn").off().click(async () => {
cancel = true;
try {
await transport.disconnect();
} catch (error) {
layer.close(index);
console.log(error);
}
});
try {
await esploader.eraseFlash();
await esploader.writeFlash(flashOptions);
layer.msg(Msg.Lang['shell.burnSucc'], { time: 1000 });
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnSucc']}==\n`);
} catch (error) {
console.log(error);
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnFailed']}==\n`);
} finally {
layer.close(index);
if (!cancel) {
await transport.disconnect();
}
}
}
});
}
BU.burnWithAdafruitEsptool = async () => {
const { web } = SELECTED_BOARD;
const { burn } = web;
const { mainStatusBarTabs } = Mixly;
const portName = Serial.getSelectedPortName();
if (!portName) {
/*layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});*/
BU.requestPort();
return;
}
const statusBarSerial = mainStatusBarTabs.getStatusBarById(portName);
if (statusBarSerial) {
await statusBarSerial.close();
}
const port = Serial.getPort(portName);
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.setValue(Msg.Lang['shell.burning'] + '...\n');
mainStatusBarTabs.show();
mainStatusBarTabs.changeTo('output');
let esploader = null;
let transport = null;
let espStub = null;
try {
await port.open({ baudRate: 115200 });
esploader = new AdafruitESPTool.ESPLoader(port, {
log(...args) {
statusBarTerminal.addValue(args.join('') + '\n');
},
debug(...args) {
statusBarTerminal.addValue(args.join('') + '\n');
},
error(...args) {
statusBarTerminal.addValue(args.join('') + '\n');
}
});
await esploader.initialize();
espStub = await esploader.runStub();
} catch (error) {
console.log(error);
statusBarTerminal.addValue(`\n${error.toString()}\n`);
await port.close();
return;
}
statusBarTerminal.addValue(Msg.Lang['shell.bin.reading'] + "...");
if (typeof burn.binFile !== 'object') {
statusBarTerminal.addValue(" Failed!\n" + Msg.Lang['shell.bin.readFailed'] + "\n");
await espStub.disconnect();
await espStub.port.close();
return;
}
const { binFile } = burn;
let firmwarePromise = [];
statusBarTerminal.addValue("\n");
for (let i of binFile) {
if (i.path && i.offset) {
let absolutePath = path.join(Env.boardDirPath, i.path);
// statusBarTerminal.addValue(`${Msg.Lang['读取固件'] + ' '
// + Msg.Lang['路径']}:${absolutePath}, ${Msg.Lang['偏移']}:${i.offset}\n`);
firmwarePromise.push(readBinFileAsArrayBuffer(absolutePath, i.offset));
}
}
let data = null;
try {
data = await Promise.all(firmwarePromise);
} catch (error) {
statusBarTerminal.addValue("Failed!\n" + Msg.Lang['shell.bin.readFailed'] + "\n");
statusBarTerminal.addValue("\n" + e + "\n", true);
await espStub.disconnect();
await espStub.port.close();
return;
}
statusBarTerminal.addValue("Done!\n");
BU.burning = true;
BU.uploading = false;
const layerNum = layer.open({
type: 1,
title: Msg.Lang['shell.burning'] + '...',
content: $('#mixly-loader-div'),
shade: LayerExt.SHADE_NAV,
resize: false,
closeBtn: 0,
success: async function (layero, index) {
let cancel = false;
$("#mixly-loader-btn").hide();
try {
for (let file of data) {
await espStub.flashData(
file.data,
(bytesWritten, totalBytes) => {
const percent = Math.floor((bytesWritten / totalBytes) * 100) + '%';
statusBarTerminal.addValue(`Writing at 0x${(file.address + bytesWritten).toString(16)}... (${percent})\n`);
},
file.address, true
);
}
await espStub.disconnect();
await espStub.port.close();
cancel = true;
layer.msg(Msg.Lang['shell.burnSucc'], { time: 1000 });
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnSucc']}==\n`);
} catch (error) {
console.log(error);
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnFailed']}==\n`);
} finally {
layer.close(index);
if (!cancel) {
await espStub.disconnect();
await espStub.port.close();
}
}
}
});
}
BU.getImportModulesName = (code) => {
const { web = {} } = SELECTED_BOARD;
const { lib } = web;
if (!(lib instanceof Object)) {
return [];
}
let lineList = [];
code.trim().split("\n").forEach(function (v, i) {
lineList.push(v);
});
let moduleName = "";
let moduleList = [];
for (let data of lineList) {
let fromLoc = data.indexOf("from");
let importLoc = data.indexOf("import");
const str = data.substring(0, (fromLoc === -1)? importLoc : fromLoc);
str.split('').forEach((ch) => {
if (ch !== ' ' && ch !== '\t') {
fromLoc = -1;
importLoc = -1;
return;
}
});
if (fromLoc !== -1) {
moduleName = data.substring(fromLoc + 4, data.indexOf("import"));
} else if (importLoc !== -1) {
moduleName = data.substring(importLoc + 6);
} else {
continue;
}
moduleName = moduleName.replaceAll(' ', '');
moduleName = moduleName.replaceAll('\r', '');
moduleList = [ ...moduleList, ...moduleName.split(",") ];
}
return moduleList;
}
BU.searchLibs = (moduleList, libList = []) => {
const { web = {} } = SELECTED_BOARD;
const { lib } = web;
if (!(lib instanceof Object)) {
return [];
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
for (let name of moduleList) {
if (!libList.includes(name)) {
if (!lib[name]) {
continue;
}
libList.push(name);
statusBarTerminal.addValue(Msg.Lang['shell.copyLib'] + ' ' + name + '.py\n');
if (!lib[name].import.length) {
continue;
}
libList = BU.searchLibs(lib[name].import, libList);
}
}
return libList;
}
BU.initUpload = () => {
const portName = Serial.getSelectedPortName();
if (!portName) {
/*layer.msg(Msg.Lang['statusbar.serial.noDevice'], {
time: 1000
});*/
BU.requestPort();
return;
}
BU.uploadWithAmpy(portName);
}
BU.uploadWithAmpy = (portName) => {
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
let statusBarSerial = mainStatusBarTabs.getStatusBarById(portName);
BU.burning = false;
BU.uploading = true;
statusBarTerminal.setValue(Msg.Lang['shell.uploading'] + '...\n');
mainStatusBarTabs.show();
mainStatusBarTabs.changeTo('output');
const mainWorkspace = Workspace.getMain();
const editor = mainWorkspace.getEditorsManager().getActive();
const layerNum = layer.open({
type: 1,
title: Msg.Lang['shell.uploading'] + '...',
content: $('#mixly-loader-div'),
shade: LayerExt.SHADE_NAV,
resize: false,
closeBtn: 0,
success: function (layero, index) {
const serial = new Serial(portName);
const ampy = new Ampy(serial);
const code = editor.getCode();
/*let moduleList = BU.getImportModulesName(code);
moduleList = BU.searchLibs(moduleList);
const moduleInfo = {};
for (let name of moduleList) {
moduleInfo[name] = SELECTED_BOARD.web.lib[name].path;
}*/
let closePromise = Promise.resolve();
if (statusBarSerial) {
closePromise = statusBarSerial.close();
}
closePromise
.then(() => ampy.enter())
.then(() => {
statusBarTerminal.addValue('Writing main.py ');
return ampy.put('main.py', code);
})
.then(() => {
statusBarTerminal.addValue('Done!\n');
return ampy.exit();
})
.then(() => ampy.dispose())
.then(() => {
layer.close(index);
layer.msg(Msg.Lang['shell.uploadSucc'], { time: 1000 });
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadSucc']}==\n`);
if (!statusBarSerial) {
mainStatusBarTabs.add('serial', portName);
statusBarSerial = mainStatusBarTabs.getStatusBarById(portName);
}
statusBarSerial.setValue('');
mainStatusBarTabs.changeTo(portName);
statusBarSerial.open().catch(Debug.error);
})
.catch((error) => {
ampy.dispose();
layer.close(index);
console.error(error);
statusBarTerminal.addValue(`${error}\n`);
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadFailed']}==\n`);
})
.finally(async () => {
BU.burning = false;
BU.uploading = false;
});
}
});
}
function hexToBuf (hex) {
var typedArray = new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16)
}));
return typedArray.buffer;
}
BU.uploadWithEsptool = async (endType, obj, layerType) => {
const portName = 'web-serial';
const portObj = Serial.portsOperator[portName];
const { serialport, toolConfig } = portObj;
let prevBaud = toolConfig.baudRates;
if (prevBaud !== 115200) {
toolConfig.baudRates = 115200;
await serialport.setBaudRate(toolConfig.baudRates);
}
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
let firmwareData = obj.data;
if (endType || typeof firmwareData !== 'object') {
statusBarTerminal.addValue(Msg.Lang['shell.bin.readFailed'] + "\n");
layer.close(layerType);
return;
}
layer.title(Msg.Lang['shell.uploading'] + '...', layerType);
statusBarTerminal.addValue(Msg.Lang['shell.bin.reading'] + "... ");
let firmwareList = [];
for (let i of firmwareData) {
if (!i.offset || !i.data) {
continue;
}
const firmware = {
offset: i.offset,
binBuf: hexToBuf(i.data)
};
firmwareList.push(firmware);
}
statusBarTerminal.addValue("Done!\n");
BU.burning = true;
BU.uploading = false;
statusBarTerminal.addValue(Msg.Lang['shell.uploading'] + '...\n');
mainStatusBarTabs.show();
mainStatusBarTabs.changeTo('output');
try {
SerialPort.refreshOutputBuffer = false;
SerialPort.refreshInputBuffer = true;
await espTool.reset();
if (await clickSync()) {
// await clickErase();
for (let i of firmwareList) {
await clickProgram(i.offset, i.binBuf);
}
}
layer.close(layerType);
layer.msg(Msg.Lang['shell.uploadSucc'], { time: 1000 });
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadSucc']}==\n`);
Serial.reset(portName, 'upload');
mainStatusBarTabs.changeTo(portName);
} catch (error) {
console.log(error);
layer.close(layerType);
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadFailed']}==\n`);
} finally {
SerialPort.refreshOutputBuffer = true;
SerialPort.refreshInputBuffer = false;
const code = MFile.getCode();
const baudRateList = code.match(/(?<=Serial.begin[\s]*\([\s]*)[0-9]*(?=[\s]*\))/g);
if (baudRateList && Serial.BAUDRATES.includes(baudRateList[0]-0)) {
prevBaud = baudRateList[0]-0;
}
if (toolConfig.baudRates !== prevBaud) {
toolConfig.baudRates = prevBaud;
await serialport.setBaudRate(prevBaud);
}
}
}
BU.uploadWithAvrUploader = async (endType, obj, layerType) => {
let firmwareData = obj.data;
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
if (endType || typeof firmwareData !== 'object') {
statusBarTerminal.addValue(Msg.Lang['shell.bin.readFailed'] + "\n");
layer.close(layerType);
return;
}
statusBarTerminal.addValue(Msg.Lang['shell.uploading'] + '...\n');
layer.title(Msg.Lang['shell.uploading'] + '...', layerType);
let uploadSucMessageShow = true;
AvrUploader.upload(firmwareData[0].data, (progress) => {
if (progress >= 100 && uploadSucMessageShow) {
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadSucc']}==\n`);
layer.msg(Msg.Lang['shell.uploadSucc'], { time: 1000 });
layer.close(layerType);
uploadSucMessageShow = false;
}
}, true)
.catch((error) => {
layer.close(layerType);
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadFailed']}==\n`);
});
}
});