Update: WebSocket下MicroPython板卡支持管理板卡文件

This commit is contained in:
王立帮
2024-12-03 19:22:22 +08:00
parent 3911fa27ac
commit b1747a3ad5
7 changed files with 245 additions and 69 deletions

104
src/common/shell-ampy.js Normal file
View File

@@ -0,0 +1,104 @@
import mustache from 'mustache';
import Shell from './shell';
import { MICROPYTHON, PYTHON } from './config';
export default class ShellAmpy extends Shell {
static {
this.TEMPLATE = {
ls: '{{&ampy}} -p {{&port}} -b {{&baud}} -i 0 ls "{{&folderPath}}"',
get: '{{&ampy}} -p {{&port}} -b {{&baud}} -i 0 get "{{&filePath}}"',
mkdir: '{{&ampy}} -p {{&port}} -b {{&baud}} -i 0 mkdir "{{&folderPath}}"',
mkfile: '{{&ampy}} -p {{&port}} -b {{&baud}} -i 0 mkfile "{{&filePath}}"',
isdir: '{{&ampy}} -p {{&port}} -b {{&baud}} -i 0 isdir "{{&folderPath}}"',
isfile: '{{&ampy}} -p {{&port}} -b {{&baud}} -i 0 isfile "{{&filePath}}"',
put: '{{&ampy}} -p {{&port}} -b {{&baud}} -i 0 put "{{&startPath}}" "{{&endPath}}"',
rm: '{{&ampy}} -p {{&port}} -b {{&baud}} -i 0 rm "{{&filePath}}"',
rmdir: '{{&ampy}} -p {{&port}} -b {{&baud}} -i 0 rmdir "{{&folderPath}}"',
rename: '{{&ampy}} -p {{&port}} -b {{&baud}} -i 0 rename "{{&oldPath}}" "{{&newPath}}"',
run: '{{&ampy}} -p {{&port}} -b {{&baud}} -i 0 run "{{&filePath}}"'
}
this.AMPY_TEMPLATE = mustache.render('"{{&python3}}" "{{&ampy}}"', {
python3: PYTHON.path.cli,
ampy: MICROPYTHON.path.ampy
});
}
constructor() {
super();
}
async ls(port, baud, folderPath) {
return this.exec(this.render('ls', { port, baud, folderPath }), {
encoding: 'utf-8'
});
}
async get(port, baud, filePath) {
return this.exec(this.render('get', { port, baud, filePath }), {
encoding: 'utf-8'
});
}
async mkdir(port, baud, folderPath) {
return this.exec(this.render('mkdir', { port, baud, folderPath }), {
encoding: 'utf-8'
});
}
async mkfile(port, baud, filePath) {
return this.exec(this.render('mkfile', { port, baud, filePath }), {
encoding: 'utf-8'
});
}
async isdir(port, baud, folderPath) {
return this.exec(this.render('isdir', { port, baud, folderPath }), {
encoding: 'utf-8'
});
}
async isfile(port, baud, filePath) {
return this.exec(this.render('isfile', { port, baud, filePath }), {
encoding: 'utf-8'
});
}
async put(port, baud, startPath, endPath) {
return this.exec(this.render('put', { port, baud, startPath, endPath }), {
encoding: 'utf-8'
});
}
async rm(port, baud, filePath) {
return this.exec(this.render('rm', { port, baud, filePath }), {
encoding: 'utf-8'
});
}
async rmdir(port, baud, folderPath) {
return this.exec(this.render('rmdir', { port, baud, folderPath }), {
encoding: 'utf-8'
});
}
async rename(port, baud, oldPath, newPath) {
return this.exec(this.render('rename', { port, baud, oldPath, newPath }), {
encoding: 'utf-8'
});
}
async run(port, baud, filePath) {
return this.exec(this.render('run', { port, baud, filePath }), {
encoding: 'utf-8'
});
}
render(templateName, args) {
return mustache.render(ShellAmpy.TEMPLATE[templateName], {
...args,
ampy: ShellAmpy.AMPY_TEMPLATE
});
}
}

View File

@@ -23,7 +23,7 @@ export default class ShellArduino extends Shell {
`"${arduino.path.code}"`,
'--no-color'
].join(' ');
return this.exec(command);
return this.execUntilClosed(command, { maxBuffer: 4096 * 1000000 });
}
async upload(config) {
@@ -43,6 +43,6 @@ export default class ShellArduino extends Shell {
`"${arduino.path.code}"`,
'--no-color'
].join(' ');
return this.exec(command);
return this.execUntilClosed(command, { maxBuffer: 4096 * 1000000 });
}
}

View File

@@ -16,7 +16,7 @@ export default class ShellMicroPython extends Shell {
com: config.port
};
const command = MString.tpl(config.command, info);
return this.exec(command);
return this.execUntilClosed(command);
}
async upload(config) {
@@ -26,6 +26,6 @@ export default class ShellMicroPython extends Shell {
com: config.port
};
const command = MString.tpl(config.command, info);
return this.exec(command);
return this.execUntilClosed(command);
}
}

View File

@@ -1,57 +1,23 @@
import { execFile, exec } from 'node:child_process';
import * as iconv_lite from 'iconv-lite';
import Debug from './debug';
import EventsBase from './events-base';
import { CURRENT_PLANTFORM } from './config';
export default class Shell extends EventsBase {
static {
this.ENCODING = CURRENT_PLANTFORM == 'win32' ? 'cp936' : 'utf-8';
}
#shell_ = null;
#killed_ = false;
#defaultOptions_ = {
maxBuffer: 4096 * 1000000,
encoding: 'binary',
};
constructor() {
super();
this.addEventsType(['data', 'error', 'close']);
}
#decode_(str) {
try {
str = decodeURIComponent(str.replace(/(_E[0-9A-F]{1}_[0-9A-F]{2}_[0-9A-F]{2})+/gm, '%$1'));
str = decodeURIComponent(str.replace(/\\(u[0-9a-fA-F]{4})/gm, '%$1'));
} catch (error) {
Debug.error(error);
}
return str;
}
#addEventsListener_() {
const { stdout, stderr } = this.#shell_;
stdout.on('data', (data) => {
if (data.length > 1000) {
return;
}
data = iconv_lite.decode(Buffer.from(data, 'binary'), 'utf-8');
this.runEvent('data', data);
});
stderr.on('data', (data) => {
let lines = data.split('\n');
for (let i in lines) {
let encoding = 'utf-8';
if (lines[i].indexOf('can\'t open device') !== -1) {
encoding = Shell.ENCODING;
}
lines[i] = iconv_lite.decode(Buffer.from(lines[i], 'binary'), encoding);
}
data = lines.join('\n');
data = this.#decode_(data);
this.runEvent('error', data);
});
}
@@ -73,42 +39,59 @@ export default class Shell extends EventsBase {
});
}
async exec(command, options = {}) {
async execUntilClosed(command, options = {}) {
this.#killed_ = false;
this.#shell_ = exec(command, { ...this.#defaultOptions_, ...options });
this.#shell_ = exec(command, options);
this.#addEventsListener_();
const result = await this.#waitUntilClosed_();
return result;
}
async execFileUntilClosed(file, args, options = {}) {
this.#killed_ = false;
this.#shell_ = execFile(file, args, options);
this.#addEventsListener_();
const result = await this.#waitUntilClosed_();
return result;
}
async exec(command, options = {}) {
return new Promise((resolve, reject) => {
this.#killed_ = false;
this.#shell_ = exec(command, options, (error, stdout) => {
if (error) {
reject(String(error));
} else {
resolve(stdout);
}
});
});
}
async execFile(file, args, options = {}) {
this.#killed_ = false;
this.#shell_ = execFile(file, args, { ...this.#defaultOptions_, ...options });
this.#addEventsListener_();
const result = await this.#waitUntilClosed_();
return result;
return new Promise((resolve, reject) => {
this.#killed_ = false;
this.#shell_ = execFile(file, args, options, (error) => {
if (error) {
reject(String(error));
} else {
resolve();
}
});
});
}
async kill() {
new Promise((resolve, reject) => {
if (this.#killed_) {
return;
}
this.#shell_.stdin.end();
this.#shell_.stdout.end();
if (CURRENT_PLANTFORM === 'win32') {
exec(`taskkill /pid ${this.#shell_.pid} /f /t`, { encoding: 'utf-8' }, (error) => {
if (error) {
reject(error);
} else {
resolve();
}
});
} else {
this.#shell_.kill('SIGTERM')
resolve();
}
})
if (this.#killed_) {
return;
}
this.#shell_.stdin.end();
this.#shell_.stdout.end();
if (CURRENT_PLANTFORM === 'win32') {
await this.exec(`taskkill /pid ${this.#shell_.pid} /f /t`);
} else {
this.#shell_.kill('SIGTERM')
}
}
getShell() {