feat(core): 优化在线版下ampy和kflash的进度显示

This commit is contained in:
王立帮
2025-11-02 19:18:11 +08:00
parent e82a2bd791
commit 535abbc8e1
5 changed files with 131 additions and 34 deletions

View File

@@ -98,6 +98,44 @@ class EditorAce extends EditorBase {
} }
} }
replaceLine(lineNumber, newContent) {
const session = this.#editor_.getSession();
const totalLines = session.getLength();
let targetLine = lineNumber < 0 ? totalLines + lineNumber : lineNumber;
if (targetLine < 0) {
targetLine = 0;
} else if (targetLine >= totalLines) {
targetLine = totalLines - 1;
}
const cursorPos = this.#editor_.getCursorPosition();
const oldLine = session.getLine(targetLine);
const range = new ace.Range(targetLine, 0, targetLine, oldLine.length);
session.replace(range, newContent);
if (cursorPos.row > targetLine) {
this.#editor_.moveCursorTo(cursorPos.row, cursorPos.column);
} else if (cursorPos.row === targetLine) {
const newCol = Math.min(cursorPos.column, newContent.length);
this.#editor_.moveCursorTo(targetLine, newCol);
} else {
this.#editor_.moveCursorTo(cursorPos.row, cursorPos.column);
}
this.#editor_.renderer.scrollCursorIntoView(null, 0.5);
}
appendLine(text) {
const session = this.#editor_.getSession();
const totalLines = session.getLength();
const lastLineIndex = totalLines - 1;
const lastLineText = session.getLine(lastLineIndex);
if (lastLineText.trim()) {
session.insert({ row: totalLines, column: 0 }, `\n${text}`);
} else {
const range = new ace.Range(lastLineIndex, 0, lastLineIndex, lastLineText.length);
session.replace(range, text);
}
this.scrollToBottom();
}
getValue() { getValue() {
return this.#destroyed_ ? '' : this.#editor_.getValue(); return this.#destroyed_ ? '' : this.#editor_.getValue();
} }

View File

@@ -1560,6 +1560,7 @@
"path", "path",
"Mustache", "Mustache",
"Mixly.Env", "Mixly.Env",
"Mixly.Events",
"Mixly.Msg", "Mixly.Msg",
"Mixly.Ampy", "Mixly.Ampy",
"Mixly.Web" "Mixly.Web"

View File

@@ -3,6 +3,7 @@ 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.Msg'); goog.require('Mixly.Msg');
goog.require('Mixly.Ampy'); goog.require('Mixly.Ampy');
goog.require('Mixly.Web'); goog.require('Mixly.Web');
@@ -10,6 +11,7 @@ goog.provide('Mixly.Web.Ampy');
const { const {
Env, Env,
Events,
Msg, Msg,
Ampy, Ampy,
Web Web
@@ -37,6 +39,7 @@ class AmpyExt extends Ampy {
#writeBuffer_ = true; #writeBuffer_ = true;
#active_ = false; #active_ = false;
#dataLength_ = 256; #dataLength_ = 256;
#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;
@@ -56,6 +59,25 @@ class AmpyExt extends Ampy {
}); });
} }
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() { isActive() {
return this.#active_; return this.#active_;
} }
@@ -235,6 +257,8 @@ class AmpyExt extends Ampy {
if (!this.isActive()) { if (!this.isActive()) {
throw new Error(Msg.Lang['ampy.portIsNotOpen']); 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); await this.exec(`file = open('${filename}', 'wb')`, timeout);
let buffer = null; let buffer = null;
if (data.constructor === String) { if (data.constructor === String) {
@@ -245,8 +269,15 @@ class AmpyExt extends Ampy {
buffer = data; buffer = data;
} }
const len = Math.ceil(buffer.length / 64); 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++) { for (let i = 0; i < len; i++) {
const writeBuffer = buffer.slice(i * 64, Math.min((i + 1) * 64, buffer.length)); 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 = ''; let writeStr = '';
for (let num of writeBuffer) { for (let num of writeBuffer) {
let numStr = num.toString(16); let numStr = num.toString(16);
@@ -258,6 +289,7 @@ class AmpyExt extends Ampy {
await this.exec(`file.write(b'${writeStr}')`, timeout); await this.exec(`file.write(b'${writeStr}')`, timeout);
} }
await this.exec('file.close()', timeout); 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) {
@@ -387,6 +419,8 @@ class AmpyExt extends Ampy {
this.#active_ = false; this.#active_ = false;
await this.#device_.dispose(); await this.#device_.dispose();
this.#device_ = null; this.#device_ = null;
this.#events_.reset();
this.#events_ = null;
} }
} }

View File

@@ -418,8 +418,6 @@ BU.burnWithKFlash = async (binFile, erase) => {
mainStatusBarTabs.changeTo('output'); mainStatusBarTabs.changeTo('output');
BU.progressLayer.title(`${Msg.Lang['shell.burning']}...`); BU.progressLayer.title(`${Msg.Lang['shell.burning']}...`);
BU.progressLayer.show(); BU.progressLayer.show();
statusBarTerminal.addValue(Msg.Lang['shell.bin.reading'] + "...");
statusBarTerminal.addValue("\n");
let data = []; let data = [];
try { try {
@@ -437,30 +435,32 @@ BU.burnWithKFlash = async (binFile, erase) => {
} }
} }
} catch (error) { } catch (error) {
statusBarTerminal.addValue("Failed!\n" + Msg.Lang['shell.bin.readFailed'] + "\n"); statusBarTerminal.addValue(`\n[ERROR] ${error}\n`, true);
statusBarTerminal.addValue("\n" + error + "\n", true);
BU.progressLayer.hide(); BU.progressLayer.hide();
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnFailed']}==\n`); statusBarTerminal.addValue(`==${Msg.Lang['shell.burnFailed']}==\n`);
return; return;
} }
statusBarTerminal.addValue("Done!\n");
let serial = null; let serial = null;
try { try {
serial = new Serial(portName); serial = new Serial(portName);
const kflash = new KFlash(serial, (message) => { const kflash = new KFlash(serial);
kflash.bind('message', (message) => {
statusBarTerminal.addValue(message); statusBarTerminal.addValue(message);
}); });
kflash.bind('replaceMessage', (lineNumber, message) => {
statusBarTerminal.replaceLine(lineNumber, message);
});
await kflash.enter(); await kflash.enter();
for (let item of data) { for (let item of data) {
await kflash.write(item.data, item.address, item.sha256Prefix ?? true, item?.filename ?? 'main.bin'); await kflash.write(item.data, item.address, item.sha256Prefix ?? true, item?.filename ?? 'main.bin');
} }
BU.progressLayer.hide(); BU.progressLayer.hide();
layer.msg(Msg.Lang['shell.burnSucc'], { time: 1000 }); layer.msg(Msg.Lang['shell.burnSucc'], { time: 1000 });
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnSucc']}==\n`); statusBarTerminal.appendLine(`==${Msg.Lang['shell.burnSucc']}==\n`);
} catch (error) { } catch (error) {
statusBarTerminal.addValue(`[ERROR] ${error.message}\n`); statusBarTerminal.appendLine(`[ERROR] ${error.message}\n`);
BU.progressLayer.hide(); BU.progressLayer.hide();
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnFailed']}==\n`); statusBarTerminal.appendLine(`==${Msg.Lang['shell.burnFailed']}==\n`);
} finally { } finally {
try { try {
serial && await serial.close(); serial && await serial.close();
@@ -640,7 +640,7 @@ BU.uploadWithAmpy = async (portName) => {
let statusBarSerial = mainStatusBarTabs.getStatusBarById(portName); let statusBarSerial = mainStatusBarTabs.getStatusBarById(portName);
BU.burning = false; BU.burning = false;
BU.uploading = true; BU.uploading = true;
statusBarTerminal.setValue(Msg.Lang['shell.uploading'] + '...\n'); statusBarTerminal.setValue(`${Msg.Lang['shell.uploading']}...\n`);
mainStatusBarTabs.show(); mainStatusBarTabs.show();
mainStatusBarTabs.changeTo('output'); mainStatusBarTabs.changeTo('output');
const mainWorkspace = Workspace.getMain(); const mainWorkspace = Workspace.getMain();
@@ -650,12 +650,21 @@ BU.uploadWithAmpy = async (portName) => {
BU.progressLayer.show(); BU.progressLayer.show();
const serial = new Serial(portName); const serial = new Serial(portName);
const ampy = new Ampy(serial); const ampy = new Ampy(serial);
ampy.bind('message', (message) => {
statusBarTerminal.addValue(message);
});
ampy.bind('replaceMessage', (lineNumber, message) => {
statusBarTerminal.replaceLine(lineNumber, message);
});
const code = editor.getCode(); const code = editor.getCode();
let closePromise = Promise.resolve(); let closePromise = Promise.resolve();
if (statusBarSerial) { if (statusBarSerial) {
closePromise = statusBarSerial.close(); closePromise = statusBarSerial.close();
} }
try { try {
await closePromise;
await ampy.enter();
await ampy.put('main.py', code);
/*const importsMap = BU.getImportModules(code); /*const importsMap = BU.getImportModules(code);
let libraries = {}; let libraries = {};
for (let key in importsMap) { for (let key in importsMap) {
@@ -665,13 +674,8 @@ BU.uploadWithAmpy = async (portName) => {
data, data,
size: importsMap[key]['__size__'] size: importsMap[key]['__size__']
}; };
}*/ }
await closePromise; let cwd = await ampy.cwd();
await ampy.enter();
statusBarTerminal.addValue('Writing main.py ');
await ampy.put('main.py', code);
statusBarTerminal.addValue('Done!\n');
/*const cwd = await ampy.cwd();
const rootInfo = await ampy.ls(cwd); const rootInfo = await ampy.ls(cwd);
let rootMap = {}; let rootMap = {};
for (let item of rootInfo) { for (let item of rootInfo) {
@@ -683,19 +687,17 @@ BU.uploadWithAmpy = async (portName) => {
if (libraries && libraries instanceof Object) { if (libraries && libraries instanceof Object) {
for (let key in libraries) { for (let key in libraries) {
if (rootMap[`${cwd}/${key}`] !== undefined && rootMap[`${cwd}/${key}`] === libraries[key].size) { if (rootMap[`${cwd}/${key}`] !== undefined && rootMap[`${cwd}/${key}`] === libraries[key].size) {
statusBarTerminal.addValue(`Skip ${key}\n`); statusBarTerminal.addValue(`Writing ${key} (Skipped)\n`);
continue; continue;
} }
statusBarTerminal.addValue(`Writing ${key} `);
await ampy.put(key, libraries[key].data); await ampy.put(key, libraries[key].data);
statusBarTerminal.addValue('Done!\n');
} }
}*/ }*/
await ampy.exit(); await ampy.exit();
await ampy.dispose(); await ampy.dispose();
BU.progressLayer.hide(); BU.progressLayer.hide();
layer.msg(Msg.Lang['shell.uploadSucc'], { time: 1000 }); layer.msg(Msg.Lang['shell.uploadSucc'], { time: 1000 });
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadSucc']}==\n`); statusBarTerminal.appendLine(`==${Msg.Lang['shell.uploadSucc']}==\n`);
if (!statusBarSerial) { if (!statusBarSerial) {
mainStatusBarTabs.add('serial', portName); mainStatusBarTabs.add('serial', portName);
statusBarSerial = mainStatusBarTabs.getStatusBarById(portName); statusBarSerial = mainStatusBarTabs.getStatusBarById(portName);
@@ -707,8 +709,8 @@ BU.uploadWithAmpy = async (portName) => {
ampy.dispose(); ampy.dispose();
BU.progressLayer.hide(); BU.progressLayer.hide();
Debug.error(error); Debug.error(error);
statusBarTerminal.addValue(`${error}\n`); statusBarTerminal.appendLine(`[ERROR] ${error.message}\n`);
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadFailed']}==\n`); statusBarTerminal.appendLine(`==${Msg.Lang['shell.uploadFailed']}==\n`);
} }
} }

View File

@@ -100,7 +100,7 @@ class MAIXLoader {
#port_ = null; #port_ = null;
#readedBuffer_ = []; #readedBuffer_ = [];
#events_ = new Events('message'); #events_ = new Events(['message', 'replaceMessage']);
constructor(port) { constructor(port) {
this.#port_ = port; this.#port_ = port;
@@ -117,6 +117,10 @@ class MAIXLoader {
this.#events_.run('message', message); this.#events_.run('message', message);
} }
replaceMessage(lineNumber, message) {
this.#events_.run('replaceMessage', lineNumber, message);
}
async write(packet) { async write(packet) {
let handlePacket = []; let handlePacket = [];
@@ -251,9 +255,9 @@ class MAIXLoader {
getProgressMessage(name, percent) { getProgressMessage(name, percent) {
const sended = parseInt(percent * 45); const sended = parseInt(percent * 45);
const left = Array(sended).fill('=').join(''); const left = percent === 0 ? '' : Array(sended).fill('=').join('');
const right = Array(45 - sended).fill('-').join(''); const right = percent === 100 ? '' : Array(45 - sended).fill('-').join('');
return `${name}: |${left}${right}| ${(percent * 100).toFixed(1)}%\n\n`; return `${name}: |${left}${right}| ${(percent * 100).toFixed(1)}%`;
} }
async flashDataFrame(data, address = 0x80000000) { async flashDataFrame(data, address = 0x80000000) {
@@ -261,6 +265,7 @@ class MAIXLoader {
const dataLength = data.length; const dataLength = data.length;
let sendedLength = 0; let sendedLength = 0;
const len = Math.ceil(dataLength / this.DATAFRAME_SIZE); const len = Math.ceil(dataLength / this.DATAFRAME_SIZE);
this.message('\n');
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
let start = i * this.DATAFRAME_SIZE; let start = i * this.DATAFRAME_SIZE;
let end = Math.min(start + this.DATAFRAME_SIZE, dataLength); let end = Math.min(start + this.DATAFRAME_SIZE, dataLength);
@@ -286,12 +291,18 @@ class MAIXLoader {
await this.write(packet); await this.write(packet);
if (await this.recvDebug()) { if (await this.recvDebug()) {
sendedLength += chunk.length; sendedLength += chunk.length;
this.message(this.getProgressMessage('Downloading ISP', sendedLength / dataLength)); const message = this.getProgressMessage('Downloading ISP', sendedLength / dataLength);
if (i > 0) {
this.replaceMessage(-1, message);
} else {
this.message(message);
}
address += this.DATAFRAME_SIZE; address += this.DATAFRAME_SIZE;
break; break;
} }
} }
} }
this.message('\n\n');
} }
async installFlashBootloader(data) { async installFlashBootloader(data) {
@@ -390,7 +401,7 @@ class MAIXLoader {
} }
async flashFirmware(firmware, aesKey = null, address = 0, sha256Prefix = true, filename = '') { async flashFirmware(firmware, aesKey = null, address = 0, sha256Prefix = true, filename = '') {
this.message(`[INFO] Writing ${filename} into ${this.formatHex(address)}\n\n`); this.message(`[INFO] Writing ${filename} into ${this.formatHex(address)}\n`);
let firmwareBin = Array.from(new Uint8Array(firmware)); let firmwareBin = Array.from(new Uint8Array(firmware));
if (sha256Prefix) { if (sha256Prefix) {
const aesCipherFlag = 0x00; const aesCipherFlag = 0x00;
@@ -432,18 +443,25 @@ class MAIXLoader {
await this.write(packet); await this.write(packet);
if (await this.recvDebug()) { if (await this.recvDebug()) {
sendedLength += chunkLength; sendedLength += chunkLength;
this.message(this.getProgressMessage('Programming BIN', sendedLength / dataLength)); const message = this.getProgressMessage('Programming BIN', sendedLength / dataLength);
if (i > 0) {
this.replaceMessage(-1, message);
} else {
this.message(`\n${message}`);
}
address += this.ISP_FLASH_DATA_FRAME_SIZE; address += this.ISP_FLASH_DATA_FRAME_SIZE;
break; break;
} }
} }
} }
this.message('\n\n');
} }
dispose() { dispose() {
this.#port_ = null; this.#port_ = null;
this.#events_.reset(); this.#events_.reset();
this.#events_ = null; this.#events_ = null;
this.#readedBuffer_ = null;
} }
} }
@@ -451,10 +469,13 @@ class KFlash {
#port_ = null; #port_ = null;
#loader_ = null; #loader_ = null;
constructor(port, messageCallback) { constructor(port) {
this.#port_ = port; this.#port_ = port;
this.#loader_ = new MAIXLoader(port); this.#loader_ = new MAIXLoader(port);
this.#loader_.bind('message', messageCallback); }
bind(...args) {
return this.#loader_.bind(...args);
} }
async enter() { async enter() {
@@ -462,7 +483,7 @@ class KFlash {
} }
async write(firmware, address = 0x0, sha256Prefix = true, filename = '') { async write(firmware, address = 0x0, sha256Prefix = true, filename = '') {
this.#loader_.message('[INFO] Trying to Enter the ISP Mode...\n'); this.#loader_.message('[INFO] Trying to Enter the ISP Mode...');
let retryCount = 0; let retryCount = 0;
while (true) { while (true) {
try { try {
@@ -500,6 +521,7 @@ class KFlash {
dispose() { dispose() {
this.#loader_.dispose(); this.#loader_.dispose();
this.#loader_ = null;
this.#port_ = null; this.#port_ = null;
} }
} }