初始化提交
This commit is contained in:
252
common/modules/mixly-modules/web/ampy-fs.js
Normal file
252
common/modules/mixly-modules/web/ampy-fs.js
Normal file
@@ -0,0 +1,252 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Mixly.FS');
|
||||
goog.require('Mixly.Debug');
|
||||
goog.require('Mixly.Web.Serial');
|
||||
goog.require('Mixly.Web.Ampy');
|
||||
goog.provide('Mixly.Web.AmpyFS');
|
||||
|
||||
const { FS, Debug, Web } = Mixly;
|
||||
const { Serial, Ampy } = Web;
|
||||
|
||||
|
||||
class AmpyFS extends FS {
|
||||
#ampy_ = null;
|
||||
#port_ = '';
|
||||
#baud_ = 115200;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#ampy_ = Ampy;
|
||||
}
|
||||
|
||||
async getAmpy() {
|
||||
const { mainStatusBarTabs } = Mixly;
|
||||
const statusBarSerial = mainStatusBarTabs.getStatusBarById(this.#port_);
|
||||
if (statusBarSerial) {
|
||||
await statusBarSerial.close();
|
||||
}
|
||||
const serial = new Serial(this.#port_);
|
||||
const ampy = new Ampy(serial);
|
||||
return ampy;
|
||||
}
|
||||
|
||||
async rename(oldPath, newPath) {
|
||||
let stdout = '', error = null, ampy = null;
|
||||
try {
|
||||
ampy = await this.getAmpy();
|
||||
await ampy.enter();
|
||||
stdout = await ampy.rename(oldPath, newPath);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
try {
|
||||
await ampy.exit();
|
||||
await ampy.dispose();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async createFile(filePath) {
|
||||
let stdout = '', error = null, ampy = null;
|
||||
try {
|
||||
ampy = await this.getAmpy();
|
||||
await ampy.enter();
|
||||
stdout = await ampy.mkfile(filePath);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
try {
|
||||
await ampy.exit();
|
||||
await ampy.dispose();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async readFile(filePath) {
|
||||
let stdout = '', error = null, ampy = null;
|
||||
try {
|
||||
ampy = await this.getAmpy();
|
||||
await ampy.enter();
|
||||
stdout = await ampy.get(filePath);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
try {
|
||||
await ampy.exit();
|
||||
await ampy.dispose();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async writeFile(filePath, data) {
|
||||
let stdout = '', error = null, ampy = null;
|
||||
try {
|
||||
ampy = await this.getAmpy();
|
||||
await ampy.enter();
|
||||
stdout = await ampy.put(filePath, data);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
try {
|
||||
await ampy.exit();
|
||||
await ampy.dispose();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async isFile(filePath) {
|
||||
let error = null;
|
||||
if (path.extname(filePath)) {
|
||||
return [error, true];
|
||||
} else {
|
||||
return [error, false];
|
||||
}
|
||||
}
|
||||
|
||||
async renameFile(oldFilePath, newFilePath) {
|
||||
return this.rename(oldFilePath, newFilePath);
|
||||
}
|
||||
|
||||
// async moveFile(oldFilePath, newFilePath) {}
|
||||
|
||||
// async copyFile(oldFilePath, newFilePath) {}
|
||||
|
||||
async deleteFile(filePath) {
|
||||
let stdout = '', error = null, ampy = null;
|
||||
try {
|
||||
ampy = await this.getAmpy();
|
||||
await ampy.enter();
|
||||
stdout = await ampy.rm(filePath);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
try {
|
||||
await ampy.exit();
|
||||
await ampy.dispose();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async createDirectory(folderPath) {
|
||||
let stdout = '', error = null, ampy = null;
|
||||
try {
|
||||
ampy = await this.getAmpy();
|
||||
await ampy.enter();
|
||||
stdout = await ampy.mkdir(folderPath);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
try {
|
||||
await ampy.exit();
|
||||
await ampy.dispose();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async readDirectory(folderPath) {
|
||||
let stdout = '', error = null, ampy = null;
|
||||
try {
|
||||
ampy = await this.getAmpy();
|
||||
await ampy.enter();
|
||||
stdout = await ampy.ls(folderPath, false, false);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
try {
|
||||
await ampy.exit();
|
||||
await ampy.dispose();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
async isDirectory(folderPath) {
|
||||
let error = null;
|
||||
if (path.extname(folderPath)) {
|
||||
return [error, false];
|
||||
} else {
|
||||
return [error, true];
|
||||
}
|
||||
}
|
||||
|
||||
async isDirectoryEmpty(folderPath) {
|
||||
return [null, false];
|
||||
}
|
||||
|
||||
async renameDirectory(oldFolderPath, newFolderPath) {
|
||||
return this.rename(oldFolderPath, newFolderPath);
|
||||
}
|
||||
|
||||
// async moveDirectory(oldFolderPath, newFolderPath) {}
|
||||
|
||||
// async copyDirectory(oldFolderPath, newFolderPath) {}
|
||||
|
||||
async deleteDirectory(folderPath) {
|
||||
let stdout = '', error = null, ampy = null;
|
||||
try {
|
||||
ampy = await this.getAmpy();
|
||||
await ampy.enter();
|
||||
stdout = await ampy.rmdir(folderPath);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
try {
|
||||
await ampy.exit();
|
||||
await ampy.dispose();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
Debug.error(error);
|
||||
}
|
||||
return [error, stdout];
|
||||
}
|
||||
|
||||
setPortName(port) {
|
||||
this.#port_ = port;
|
||||
}
|
||||
|
||||
getPortName() {
|
||||
return this.#port_;
|
||||
}
|
||||
|
||||
setBaudRate(baud) {
|
||||
this.#baud_ = baud;
|
||||
}
|
||||
|
||||
getBaudRate() {
|
||||
return this.#baud_;
|
||||
}
|
||||
}
|
||||
|
||||
Web.AmpyFS = AmpyFS;
|
||||
|
||||
});
|
||||
370
common/modules/mixly-modules/web/ampy.js
Normal file
370
common/modules/mixly-modules/web/ampy.js
Normal file
@@ -0,0 +1,370 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Mustache');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.Web');
|
||||
goog.provide('Mixly.Web.Ampy');
|
||||
|
||||
const { Env, Msg, Web } = Mixly;
|
||||
|
||||
|
||||
class Ampy {
|
||||
static {
|
||||
this.LS = goog.get(path.join(Env.templatePath, 'python/ls.py'));
|
||||
this.LS_RECURSIVE = goog.get(path.join(Env.templatePath, 'python/ls-recursive.py'));
|
||||
this.LS_LONG_FORMAT = goog.get(path.join(Env.templatePath, 'python/ls-long-format.py'));
|
||||
this.MKDIR = goog.get(path.join(Env.templatePath, 'python/mkdir.py'));
|
||||
this.MKFILE = goog.get(path.join(Env.templatePath, 'python/mkfile.py'));
|
||||
this.RENAME = goog.get(path.join(Env.templatePath, 'python/rename.py'));
|
||||
this.RM = goog.get(path.join(Env.templatePath, 'python/RM.py'));
|
||||
this.RMDIR = goog.get(path.join(Env.templatePath, 'python/rmdir.py'));
|
||||
this.GET = goog.get(path.join(Env.templatePath, 'python/get.py'));
|
||||
}
|
||||
|
||||
#device_ = null;
|
||||
#receiveTemp_ = [];
|
||||
#writeBuffer_ = false;
|
||||
#active_ = false;
|
||||
constructor(device) {
|
||||
this.#device_ = device;
|
||||
this.#addEventsListener_();
|
||||
}
|
||||
|
||||
#addEventsListener_() {
|
||||
this.#device_.bind('onChar', (char) => {
|
||||
if (['\r', '\n'].includes(char)) {
|
||||
this.#receiveTemp_.push('');
|
||||
} else {
|
||||
let line = this.#receiveTemp_.pop() ?? '';
|
||||
this.#receiveTemp_.push(line + char);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#unhexlify_(hexString) {
|
||||
if (hexString.length % 2 !== 0) {
|
||||
hexString = hexString + '0';
|
||||
}
|
||||
let bytes = [];
|
||||
for (let c = 0; c < hexString.length; c += 2) {
|
||||
bytes.push(parseInt(hexString.substr(c, 2), 16));
|
||||
}
|
||||
return new Uint8Array(bytes);
|
||||
}
|
||||
|
||||
isActive() {
|
||||
return this.#active_;
|
||||
}
|
||||
|
||||
async readUntil(ending, withEnding = true, timeout = 1000) {
|
||||
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) {
|
||||
throw new Error(ending + '查找失败');
|
||||
}
|
||||
if (!this.isActive()) {
|
||||
throw new Error('数据读取中断');
|
||||
}
|
||||
await this.#device_.sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
async interrupt(timeout = 5000) {
|
||||
// 中断两次
|
||||
await this.#device_.sendBuffer([3, 3]);
|
||||
await this.#device_.sleep(100);
|
||||
let succeed = false;
|
||||
if (await this.readUntil('>>>', true, timeout)) {
|
||||
succeed = true;
|
||||
}
|
||||
return succeed;
|
||||
}
|
||||
|
||||
async enterRawREPL(timeout = 5000) {
|
||||
await this.#device_.sendBuffer([1]);
|
||||
await this.#device_.sleep(100);
|
||||
let succeed = false;
|
||||
if (await this.readUntil('raw repl; ctrl-b to exit', true, timeout)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async exitRawREPL(timeout = 5000) {
|
||||
await this.#device_.sendBuffer([2]);
|
||||
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([4]);
|
||||
await this.#device_.sleep(100);
|
||||
let succeed = false;
|
||||
if (await this.readUntil('soft reboot', false, timeout)) {
|
||||
succeed = true;
|
||||
}
|
||||
return succeed;
|
||||
}
|
||||
|
||||
async exec(str) {
|
||||
if (this.#writeBuffer_) {
|
||||
const buffer = this.#device_.encode(str);
|
||||
const len = Math.ceil(buffer.length / 256);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const writeBuffer = buffer.slice(i * 256, Math.min((i + 1) * 256, buffer.length));
|
||||
await this.#device_.sendBuffer(writeBuffer);
|
||||
await this.#device_.sleep(10);
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < str.length / 60; i++) {
|
||||
let newData = str.substring(i * 60, (i + 1) * 60);
|
||||
await this.#device_.sendString(newData);
|
||||
await this.#device_.sleep(10);
|
||||
}
|
||||
}
|
||||
await this.#device_.sendBuffer([4]);
|
||||
}
|
||||
|
||||
async enter() {
|
||||
if (this.isActive()) {
|
||||
return;
|
||||
}
|
||||
await this.#device_.open(115200);
|
||||
await this.#device_.sleep(1000);
|
||||
await this.#device_.sendBuffer([2]);
|
||||
if (!await this.interrupt()) {
|
||||
throw new Error(Msg.Lang['ampy.interruptFailed']);
|
||||
}
|
||||
if (!await this.enterRawREPL()) {
|
||||
throw new Error(Msg.Lang['ampy.enterRawREPLFailed']);
|
||||
}
|
||||
this.#active_ = true;
|
||||
}
|
||||
|
||||
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, timeout = 5000) {
|
||||
if (!this.isActive()) {
|
||||
throw new Error('串口未打开');
|
||||
}
|
||||
const code = Mustache.render(Ampy.GET, {
|
||||
path: filename
|
||||
});
|
||||
await this.exec(code);
|
||||
await this.#device_.sleep(100);
|
||||
if (!await this.readUntil('ok', true, timeout)) {
|
||||
throw new Error('无法执行python代码');
|
||||
}
|
||||
let str = await this.readUntil('>', false, timeout);
|
||||
str = str.replace('\x04\x04', '');
|
||||
if (str.indexOf('OSError') === -1) {
|
||||
str = this.#device_.decode(this.#unhexlify_(str));
|
||||
return str;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async put(filename, code) {
|
||||
if (!this.isActive()) {
|
||||
throw new Error('串口未打开');
|
||||
}
|
||||
let str = `file = open('${filename}', 'wb')\n`;
|
||||
const buffer = this.#device_.encode(code);
|
||||
const len = Math.ceil(buffer.length / 500);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const writeBuffer = buffer.slice(i * 500, Math.min((i + 1) * 500, buffer.length));
|
||||
let writeStr = '';
|
||||
for (let num of writeBuffer) {
|
||||
let numStr = num.toString(16);
|
||||
if (numStr.length === 1) {
|
||||
numStr = '0' + numStr;
|
||||
}
|
||||
writeStr += '\\x' + numStr;
|
||||
}
|
||||
str += `file.write(b'${writeStr}')\n`;
|
||||
}
|
||||
str += `file.close()\n`;
|
||||
await this.exec(str);
|
||||
}
|
||||
|
||||
async ls(directory = '/', longFormat = true, recursive = false, timeout = 5000) {
|
||||
if (!this.isActive()) {
|
||||
throw new Error('串口未打开');
|
||||
}
|
||||
let code = '';
|
||||
if (longFormat) {
|
||||
code = Mustache.render(Ampy.LS_LONG_FORMAT, {
|
||||
path: directory
|
||||
});
|
||||
} else if (recursive) {
|
||||
code = Mustache.render(Ampy.LS_RECURSIVE, {
|
||||
path: directory
|
||||
});
|
||||
} else {
|
||||
code = Mustache.render(Ampy.LS, {
|
||||
path: directory
|
||||
});
|
||||
}
|
||||
await this.exec(code);
|
||||
await this.#device_.sleep(100);
|
||||
if (!await this.readUntil('ok', true, timeout)) {
|
||||
throw new Error('无法执行python代码');
|
||||
}
|
||||
let str = await this.readUntil('>', false, timeout);
|
||||
let info = null;
|
||||
str = str.replace('\x04\x04', '');
|
||||
str = str.replaceAll('\'', '\"');
|
||||
str = str.substring(0, str.lastIndexOf(']') + 1);
|
||||
info = JSON.parse(str);
|
||||
return info;
|
||||
}
|
||||
|
||||
async mkdir(directory, timeout = 5000) {
|
||||
if (!this.isActive()) {
|
||||
throw new Error('串口未打开');
|
||||
}
|
||||
const code = Mustache.render(Ampy.MKDIR, {
|
||||
path: directory
|
||||
});
|
||||
await this.exec(code);
|
||||
await this.#device_.sleep(100);
|
||||
if (!await this.readUntil('ok', true, timeout)) {
|
||||
throw new Error('无法执行python代码');
|
||||
}
|
||||
let str = await this.readUntil('>', false, timeout);
|
||||
if (str.indexOf('OSError') === -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async mkfile(file, timeout = 5000) {
|
||||
if (!this.isActive()) {
|
||||
throw new Error('串口未打开');
|
||||
}
|
||||
const code = Mustache.render(Ampy.MKFILE, {
|
||||
path: file
|
||||
});
|
||||
await this.exec(code);
|
||||
await this.#device_.sleep(100);
|
||||
if (!await this.readUntil('ok', true, timeout)) {
|
||||
throw new Error('无法执行python代码');
|
||||
}
|
||||
let str = await this.readUntil('>', false, timeout);
|
||||
if (str.indexOf('OSError') === -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async rename(oldname, newname, timeout = 5000) {
|
||||
if (!this.isActive()) {
|
||||
throw new Error('串口未打开');
|
||||
}
|
||||
const code = Mustache.render(Ampy.RENAME, {
|
||||
oldPath: oldname,
|
||||
newPath: newname
|
||||
});
|
||||
await this.exec(code);
|
||||
await this.#device_.sleep(100);
|
||||
if (!await this.readUntil('ok', true, timeout)) {
|
||||
throw new Error('无法执行python代码');
|
||||
}
|
||||
let str = await this.readUntil('>', false, timeout);
|
||||
if (str.indexOf('OSError') === -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async rm(filename, timeout = 5000) {
|
||||
if (!this.isActive()) {
|
||||
throw new Error('串口未打开');
|
||||
}
|
||||
const code = Mustache.render(Ampy.RM, {
|
||||
path: filename
|
||||
});
|
||||
await this.exec(code);
|
||||
await this.#device_.sleep(100);
|
||||
if (!await this.readUntil('ok', true, timeout)) {
|
||||
throw new Error('无法执行python代码');
|
||||
}
|
||||
let str = await this.readUntil('>', false, timeout);
|
||||
if (str.indexOf('OSError') === -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async rmdir(directory, timeout = 5000) {
|
||||
if (!this.isActive()) {
|
||||
throw new Error('串口未打开');
|
||||
}
|
||||
const code = Mustache.render(Ampy.RMDIR, {
|
||||
path: directory
|
||||
});
|
||||
await this.exec(code);
|
||||
await this.#device_.sleep(100);
|
||||
if (!await this.readUntil('ok', true, timeout)) {
|
||||
throw new Error('无法执行python代码');
|
||||
}
|
||||
let str = await this.readUntil('>', false, timeout);
|
||||
if (str.indexOf('OSError') === -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getDevice() {
|
||||
return this.#device_;
|
||||
}
|
||||
|
||||
async dispose() {
|
||||
this.#active_ = false;
|
||||
await this.#device_.dispose();
|
||||
this.#device_ = null;
|
||||
}
|
||||
}
|
||||
|
||||
Web.Ampy = Ampy;
|
||||
|
||||
});
|
||||
185
common/modules/mixly-modules/web/bluetooth.js
Normal file
185
common/modules/mixly-modules/web/bluetooth.js
Normal file
@@ -0,0 +1,185 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('Mixly.Web');
|
||||
goog.provide('Mixly.Web.Bluetooth');
|
||||
|
||||
const { Web } = Mixly;
|
||||
const { Bluetooth } = Web;
|
||||
|
||||
Bluetooth.output = [];
|
||||
Bluetooth.mtu = 100;
|
||||
Bluetooth.encoder = new TextEncoder('utf-8');
|
||||
Bluetooth.decoder = new TextDecoder('utf-8');
|
||||
Bluetooth.nordicUartServiceUuid = 0xfff0;
|
||||
Bluetooth.uartRxCharacteristicUuid = 0xfff2;
|
||||
Bluetooth.uartTxCharacteristicUuid = 0xfff1;
|
||||
Bluetooth.obj = null;
|
||||
Bluetooth.server = null;
|
||||
Bluetooth.service = null;
|
||||
Bluetooth.uartRxCharacteristic = null;
|
||||
Bluetooth.uartTxCharacteristic = null;
|
||||
Bluetooth.name = 'bluetooth';
|
||||
|
||||
Bluetooth.connect = (baud = 115200, onDataLine = (message) => {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (Bluetooth.isConnected()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
navigator.bluetooth.requestDevice({
|
||||
// filters: [{name: ['Mixly']}]
|
||||
optionalServices: [Bluetooth.nordicUartServiceUuid],
|
||||
acceptAllDevices: true
|
||||
})
|
||||
.then((device) => {
|
||||
Bluetooth.obj = device;
|
||||
return device.gatt.connect();
|
||||
})
|
||||
.then((server) => {
|
||||
Bluetooth.server = server;
|
||||
return server.getPrimaryService(Bluetooth.nordicUartServiceUuid);
|
||||
})
|
||||
.then((service) => {
|
||||
Bluetooth.service = service;
|
||||
return service.getCharacteristic(Bluetooth.uartRxCharacteristicUuid);
|
||||
})
|
||||
.then((uartRxCharacteristic) => {
|
||||
Bluetooth.uartRxCharacteristic = uartRxCharacteristic;
|
||||
return Bluetooth.service.getCharacteristic(Bluetooth.uartTxCharacteristicUuid);
|
||||
})
|
||||
.then((uartTxCharacteristic) => {
|
||||
Bluetooth.uartTxCharacteristic = uartTxCharacteristic;
|
||||
return uartTxCharacteristic.startNotifications();
|
||||
})
|
||||
.then(() => {
|
||||
Bluetooth.onDataLine = onDataLine;
|
||||
Bluetooth.addReadEvent(onDataLine);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
Bluetooth.obj = null;
|
||||
Bluetooth.server = null;
|
||||
Bluetooth.service = null;
|
||||
Bluetooth.uartRxCharacteristic = null;
|
||||
Bluetooth.uartTxCharacteristic = null;
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Bluetooth.close = async () => {
|
||||
if (Bluetooth.isConnected()) {
|
||||
await Bluetooth.obj.gatt.disconnect();
|
||||
Bluetooth.obj = null;
|
||||
Bluetooth.server = null;
|
||||
Bluetooth.service = null;
|
||||
Bluetooth.uartRxCharacteristic = null;
|
||||
Bluetooth.uartTxCharacteristic = null;
|
||||
}
|
||||
}
|
||||
|
||||
Bluetooth.isConnected = () => {
|
||||
return Bluetooth.obj && Bluetooth.obj.gatt.connected;
|
||||
}
|
||||
|
||||
Bluetooth.addReadEvent = (onDataLine = (message) => {}) => {
|
||||
Bluetooth.uartTxCharacteristic.addEventListener('characteristicvaluechanged', event => {
|
||||
let data = Bluetooth.decoder.decode(event.target.value);
|
||||
let dataList = data.split('\n');
|
||||
if (!dataList.length) {
|
||||
return;
|
||||
}
|
||||
let endStr = '';
|
||||
if (Bluetooth.output.length) {
|
||||
endStr = Bluetooth.output.pop();
|
||||
Bluetooth.output.push(endStr + dataList.shift());
|
||||
if (dataList.length) {
|
||||
// console.log(Bluetooth.output[Bluetooth.output.length - 1]);
|
||||
onDataLine(Bluetooth.output[Bluetooth.output.length - 1]);
|
||||
}
|
||||
}
|
||||
let i = 0;
|
||||
for (let value of dataList) {
|
||||
i++;
|
||||
Bluetooth.output.push(value);
|
||||
if (i < dataList.length) {
|
||||
// console.log(value);
|
||||
onDataLine(value);
|
||||
}
|
||||
}
|
||||
while (Bluetooth.output.length > 500) {
|
||||
Bluetooth.output.shift();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Bluetooth.AddOnConnectEvent = (onConnect) => {
|
||||
}
|
||||
|
||||
Bluetooth.AddOnDisconnectEvent = (onDisconnect) => {
|
||||
Bluetooth.obj.addEventListener('gattserverdisconnected', () => {
|
||||
onDisconnect();
|
||||
});
|
||||
}
|
||||
|
||||
Bluetooth.writeString = async (str) => {
|
||||
let buffer = Bluetooth.encoder.encode(str);
|
||||
await Bluetooth.writeByteArr(buffer);
|
||||
}
|
||||
|
||||
Bluetooth.writeByteArr = async (buffer) => {
|
||||
buffer = new Uint8Array(buffer);
|
||||
for (let chunk = 0; chunk < Math.ceil(buffer.length / Bluetooth.mtu); chunk++) {
|
||||
let start = Bluetooth.mtu * chunk;
|
||||
let end = Bluetooth.mtu * (chunk + 1);
|
||||
await Bluetooth.uartRxCharacteristic.writeValueWithResponse(buffer.slice(start, end))
|
||||
.catch(error => {
|
||||
if (error == "NetworkError: GATT operation already in progress.") {
|
||||
Bluetooth.writeByteArr(buffer);
|
||||
}
|
||||
else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
await Bluetooth.sleep(200);
|
||||
}
|
||||
|
||||
Bluetooth.writeCtrlA = async () => {
|
||||
await Bluetooth.writeByteArr([1, 13, 10]);
|
||||
}
|
||||
|
||||
Bluetooth.writeCtrlB = async () => {
|
||||
await Bluetooth.writeByteArr([2, 13, 10]);
|
||||
}
|
||||
|
||||
Bluetooth.writeCtrlC = async () => {
|
||||
await Bluetooth.writeByteArr([3, 13, 10]);
|
||||
}
|
||||
|
||||
Bluetooth.writeCtrlD = async () => {
|
||||
await Bluetooth.writeByteArr([3, 4]);
|
||||
}
|
||||
|
||||
Bluetooth.write = async (type, data, dataTail) => {
|
||||
switch (type) {
|
||||
case 'string':
|
||||
return Bluetooth.writeString(data + dataTail);
|
||||
break;
|
||||
default:
|
||||
await Bluetooth.writeByteArr(data);
|
||||
return Bluetooth.writeString(dataTail);
|
||||
}
|
||||
}
|
||||
|
||||
Bluetooth.setSignals = async (dtr, rts) => {
|
||||
}
|
||||
|
||||
Bluetooth.setBaudRate = async (baud) => {
|
||||
}
|
||||
|
||||
Bluetooth.sleep = (ms) => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
});
|
||||
539
common/modules/mixly-modules/web/burn-upload.js
Normal file
539
common/modules/mixly-modules/web/burn-upload.js
Normal file
@@ -0,0 +1,539 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('ESPTool');
|
||||
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;
|
||||
|
||||
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) => {
|
||||
resolve({
|
||||
offset: offset,
|
||||
blob: null
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
BU.initBurn = () => {
|
||||
if (SELECTED_BOARD.web.com === 'usb') {
|
||||
BU.burnByUSB();
|
||||
} 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['固件读取出错'], { 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
|
||||
});
|
||||
return;
|
||||
}
|
||||
const statusBarSerial = mainStatusBarTabs.getStatusBarById(portName);
|
||||
if (statusBarSerial) {
|
||||
await statusBarSerial.close();
|
||||
}
|
||||
const port = Serial.getPort(portName);
|
||||
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
|
||||
statusBarTerminal.addValue(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: 921600,
|
||||
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.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.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
|
||||
});
|
||||
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`);
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
47
common/modules/mixly-modules/web/file-tree.js
Normal file
47
common/modules/mixly-modules/web/file-tree.js
Normal file
@@ -0,0 +1,47 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Mixly.FileTree');
|
||||
goog.require('Mixly.Web.FS');
|
||||
goog.provide('Mixly.Web.FileTree');
|
||||
|
||||
const { FileTree, Web } = Mixly;
|
||||
const { FS } = Web;
|
||||
|
||||
class FileTreeExt extends FileTree {
|
||||
constructor(element, mprogress) {
|
||||
super(element, mprogress, FS);
|
||||
}
|
||||
|
||||
async readFolder(inPath) {
|
||||
const fs = this.getFS();
|
||||
const status = await fs.isDirectory(inPath);
|
||||
let output = [];
|
||||
if (!status) {
|
||||
return output;
|
||||
}
|
||||
const children = await fs.readDirectory(inPath);
|
||||
for (let data of children) {
|
||||
const dataPath = path.join(inPath, data);
|
||||
if (await fs.isDirectory(dataPath)) {
|
||||
const isDirEmpty = await fs.isDirectoryEmpty(dataPath);
|
||||
output.push({
|
||||
type: 'folder',
|
||||
id: dataPath,
|
||||
children: !isDirEmpty
|
||||
});
|
||||
} else {
|
||||
output.push({
|
||||
type: 'file',
|
||||
id: dataPath,
|
||||
children: false
|
||||
});
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
Web.FileTree = FileTreeExt;
|
||||
|
||||
});
|
||||
200
common/modules/mixly-modules/web/file.js
Normal file
200
common/modules/mixly-modules/web/file.js
Normal file
@@ -0,0 +1,200 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Blockly');
|
||||
goog.require('Mixly.MFile');
|
||||
goog.require('Mixly.LayerExt');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.Workspace');
|
||||
goog.provide('Mixly.Web.File');
|
||||
|
||||
const {
|
||||
MFile,
|
||||
Web,
|
||||
LayerExt,
|
||||
Msg,
|
||||
Title,
|
||||
Workspace
|
||||
} = Mixly;
|
||||
|
||||
const { MSG } = Blockly.Msg;
|
||||
|
||||
const { File } = Web;
|
||||
|
||||
File.obj = null;
|
||||
|
||||
File.open = async () => {
|
||||
if (window.location.protocol === 'https:') {
|
||||
let filters = [];
|
||||
MFile.openFilters.map((data) => {
|
||||
filters.push('.' + data);
|
||||
});
|
||||
const fileConfig = {
|
||||
multiple: false,
|
||||
types: [{
|
||||
description: 'Mixly File',
|
||||
accept: {
|
||||
'application/xml': filters
|
||||
}
|
||||
}],
|
||||
suggestedStartLocation: 'pictures-library'
|
||||
};
|
||||
try {
|
||||
const [ obj ] = await window.showOpenFilePicker(fileConfig);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
File.obj = obj;
|
||||
const extname = path.extname(obj.name);
|
||||
const fileInfo = await File.obj.getFile();
|
||||
if (!fileInfo) {
|
||||
return;
|
||||
}
|
||||
File.parseData(extname, await fileInfo.text());
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
} else {
|
||||
const filters = '.' + MFile.openFilters.join(',.');
|
||||
MFile.openFile(filters, 'text', (fileObj) => {
|
||||
let { data, filename } = fileObj;
|
||||
const extname = path.extname(filename);
|
||||
File.parseData(extname, data);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
File.parseData = (extname, text) => {
|
||||
if (['.bin', '.hex'].includes(extname)) {
|
||||
MFile.loadHex(text);
|
||||
} else if (['.mix', '.xml', '.ino', '.py'].includes(extname)) {
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
editor.setValue(text, extname);
|
||||
} else {
|
||||
layer.msg(Msg.Lang['文件后缀错误'], { time: 1000 });
|
||||
File.obj = null;
|
||||
}
|
||||
}
|
||||
|
||||
File.save = async () => {
|
||||
window.userEvents && window.userEvents.addRecord({
|
||||
operation: 'save'
|
||||
});
|
||||
if (!File.obj) {
|
||||
File.saveAs();
|
||||
return;
|
||||
}
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
let text = '';
|
||||
const extname = path.extname(File.obj.name);
|
||||
switch (extname) {
|
||||
case '.mix':
|
||||
case '.xml':
|
||||
text = editor.getValue();
|
||||
break;
|
||||
case '.ino':
|
||||
case '.py':
|
||||
text = editor.getCode();
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const writer = await File.obj.createWritable();
|
||||
await writer.write(text);
|
||||
await writer.close();
|
||||
layer.msg('写入新数据到' + File.obj.name, { time: 1000 });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
File.saveAs = async () => {
|
||||
let filters = [];
|
||||
MFile.saveFilters.map((data) => {
|
||||
filters.push('.' + data.extensions[0]);
|
||||
});
|
||||
const fileConfig = {
|
||||
types: [{
|
||||
description: 'Mixly File',
|
||||
accept: {
|
||||
'application/xml': filters
|
||||
}
|
||||
}]
|
||||
};
|
||||
try {
|
||||
const obj = await window.showSaveFilePicker(fileConfig);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
File.obj = obj;
|
||||
File.save();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
File.new = async () => {
|
||||
const mainWorkspace = Workspace.getMain();
|
||||
const editor = mainWorkspace.getEditorsManager().getActive();
|
||||
const blockEditor = editor.getPage('block').getEditor();
|
||||
const codeEditor = editor.getPage('code').getEditor();
|
||||
const generator = Blockly.generator;
|
||||
const blocksList = blockEditor.getAllBlocks();
|
||||
if (editor.getPageType() === 'code') {
|
||||
const code = codeEditor.getValue(),
|
||||
workspaceToCode = generator.workspaceToCode(blockEditor) || '';
|
||||
if (!blocksList.length && workspaceToCode === code) {
|
||||
layer.msg(Msg.Lang['代码区已清空'], { time: 1000 });
|
||||
File.obj = null;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!blocksList.length) {
|
||||
layer.msg(Msg.Lang['工作区已清空'], { time: 1000 });
|
||||
File.obj = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
layer.confirm(MSG['confirm_newfile'], {
|
||||
title: false,
|
||||
shade: LayerExt.SHADE_ALL,
|
||||
resize: false,
|
||||
success: (layero) => {
|
||||
const { classList } = layero[0].childNodes[1].childNodes[0];
|
||||
classList.remove('layui-layer-close2');
|
||||
classList.add('layui-layer-close1');
|
||||
},
|
||||
btn: [MSG['newfile_yes'], MSG['newfile_no']],
|
||||
btn2: (index, layero) => {
|
||||
layer.close(index);
|
||||
}
|
||||
}, (index, layero) => {
|
||||
layer.close(index);
|
||||
blockEditor.clear();
|
||||
blockEditor.scrollCenter();
|
||||
Blockly.hideChaff();
|
||||
codeEditor.setValue(generator.workspaceToCode(blockEditor) || '', -1);
|
||||
File.obj = null;
|
||||
});
|
||||
}
|
||||
|
||||
File.saveCode = () => {
|
||||
|
||||
}
|
||||
|
||||
File.saveMix = () => {
|
||||
|
||||
}
|
||||
|
||||
File.saveImg = () => {
|
||||
|
||||
}
|
||||
|
||||
File.saveHex = () => {
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
87
common/modules/mixly-modules/web/footerlayer-example.js
Normal file
87
common/modules/mixly-modules/web/footerlayer-example.js
Normal file
@@ -0,0 +1,87 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('path');
|
||||
goog.require('Mixly.Config');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.MJSON');
|
||||
goog.require('Mixly.FooterLayerExample');
|
||||
goog.require('Mixly.Boards');
|
||||
goog.provide('Mixly.Web.FooterLayerExample');
|
||||
|
||||
const {
|
||||
Config,
|
||||
Env,
|
||||
FooterLayerExample,
|
||||
MJSON,
|
||||
Boards,
|
||||
Web
|
||||
} = Mixly;
|
||||
|
||||
const { BOARD } = Config;
|
||||
|
||||
class FooterLayerExampleExt extends FooterLayerExample {
|
||||
static DIR_TREE = MJSON.get(path.join(Env.boardDirPath, 'examples/map.json')) ?? [];
|
||||
|
||||
constructor(element) {
|
||||
super(element);
|
||||
}
|
||||
|
||||
getRoot() {
|
||||
const { DIR_TREE } = FooterLayerExampleExt;
|
||||
let exampleList = [];
|
||||
if (DIR_TREE instanceof Object) {
|
||||
exampleList = [{
|
||||
title: BOARD.boardType,
|
||||
id: '',
|
||||
children: []
|
||||
}];
|
||||
}
|
||||
return exampleList;
|
||||
}
|
||||
|
||||
getChildren(inPath) {
|
||||
const { DIR_TREE } = FooterLayerExampleExt;
|
||||
let pathList = [];
|
||||
if (inPath) {
|
||||
pathList = inPath.split('/');
|
||||
}
|
||||
let obj = DIR_TREE;
|
||||
for (let key of pathList) {
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
if (obj[key]) {
|
||||
obj = obj[key];
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
if (!(obj instanceof Object)) {
|
||||
return [];
|
||||
}
|
||||
let exampleList = [];
|
||||
for (let key in obj) {
|
||||
if (!(obj[key] instanceof Object)) {
|
||||
continue;
|
||||
}
|
||||
const exampleObj = {
|
||||
title: obj[key]['__name__'],
|
||||
id: inPath ? (inPath + '/' + key) : key
|
||||
};
|
||||
if (!obj[key]['__file__']) {
|
||||
exampleObj.children = [];
|
||||
}
|
||||
exampleList.push(exampleObj);
|
||||
}
|
||||
return exampleList;
|
||||
}
|
||||
|
||||
dataToWorkspace(inPath) {
|
||||
const data = goog.get(path.join(Env.boardDirPath, 'examples', inPath));
|
||||
this.updateCode(inPath.substring(inPath.lastIndexOf('.')), data);
|
||||
}
|
||||
}
|
||||
|
||||
Web.FooterLayerExample = FooterLayerExampleExt;
|
||||
|
||||
});
|
||||
131
common/modules/mixly-modules/web/fs.js
Normal file
131
common/modules/mixly-modules/web/fs.js
Normal file
@@ -0,0 +1,131 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('workerpool');
|
||||
goog.require('Mixly.Web');
|
||||
goog.provide('Mixly.Web.FS');
|
||||
|
||||
const { FS } = Mixly.Web;
|
||||
|
||||
FS.pool = workerpool.pool('../common/modules/mixly-modules/workers/web/file-system-access.js', {
|
||||
workerOpts: {
|
||||
name: 'fileSystemAccess'
|
||||
},
|
||||
workerType: 'web'
|
||||
});
|
||||
|
||||
FS.showOpenFilePicker = async () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
FS.showDirectoryPicker = async () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
window.showDirectoryPicker({
|
||||
mode: 'readwrite'
|
||||
})
|
||||
.then((filesystem) => {
|
||||
return FS.pool.exec('addFileSystemHandler', [filesystem]);
|
||||
})
|
||||
.then((folderPath) => {
|
||||
resolve(folderPath);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
FS.showSaveFilePicker = async () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
FS.readFile = (path) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const [data] = await FS.pool.exec('readFile', [path, 'utf8']);
|
||||
resolve(data);
|
||||
});
|
||||
}
|
||||
|
||||
FS.writeFile = (path, data) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const [error, entries] = await FS.pool.exec('writeFile', [path, data, 'utf8']);
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(entries);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FS.isFile = (path) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const [_, stats] = await FS.pool.exec('stat', [path]);
|
||||
if (!stats) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
if (stats.mode === 33188) {
|
||||
resolve(true);
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FS.readDirectory = (path) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const [error, entries] = await FS.pool.exec('readdir', [path]);
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(entries);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FS.isDirectory = (path) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const [_, stats] = await FS.pool.exec('stat', [path]);
|
||||
if (!stats) {
|
||||
resolve(false);
|
||||
return;
|
||||
}
|
||||
if (stats.mode === 33188) {
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FS.isDirectoryEmpty = async (path) => {
|
||||
return !(await FS.readDirectory(path) ?? []).length;
|
||||
}
|
||||
|
||||
FS.moveDirectory = (oldFolderPath, newFolderPath) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
FS.copyDirectory = (oldFolderPath, newFolderPath) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
FS.deleteDirectory = (folderPath) => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const [error] = await FS.pool.exec('rmdir', [folderPath]);
|
||||
if (!error) {
|
||||
resolve();
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
139
common/modules/mixly-modules/web/lms.js
Normal file
139
common/modules/mixly-modules/web/lms.js
Normal file
@@ -0,0 +1,139 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('saveAs');
|
||||
goog.require('Blob');
|
||||
goog.require('Blockly');
|
||||
goog.require('Mixly.MFile');
|
||||
goog.require('Mixly.Config');
|
||||
goog.require('Mixly.MicrobitFs');
|
||||
goog.require('Mixly.LocalStorage');
|
||||
goog.require('Mixly.Web.File');
|
||||
goog.provide('Mixly.Web.Lms');
|
||||
|
||||
const {
|
||||
Web,
|
||||
MFile,
|
||||
Config,
|
||||
MicrobitFs,
|
||||
LocalStorage
|
||||
} = Mixly;
|
||||
|
||||
const { File, Lms } = Web;
|
||||
|
||||
const { BOARD } = Config;
|
||||
|
||||
const DOM_STR = `
|
||||
<li class="layui-nav-item" lay-unselect="">
|
||||
<a href="#" class="icon-upload">保存到教学平台</a>
|
||||
</li>
|
||||
`;
|
||||
|
||||
Lms.getUrlParam = function(name) {
|
||||
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); // 构造一个含有目标参数的正则表达式对象
|
||||
var r = window.location.search.substr(1).match(reg); // 匹配目标参数
|
||||
if (r != null) return unescape(r[2]); return null; // 返回参数值
|
||||
}
|
||||
|
||||
Lms.save2moodle = function() {
|
||||
var id = Lms.getUrlParam('id');
|
||||
var hash = Lms.getUrlParam('hash');
|
||||
var userid = Lms.getUrlParam('userid');
|
||||
var taskid = Lms.getUrlParam('taskid');
|
||||
if (id == null || hash == null || userid == null) {
|
||||
alert('参数有误,请检查(请从作业进入)');
|
||||
return false;
|
||||
}
|
||||
var data = '';
|
||||
data = MFile.getCode();
|
||||
type = 'py';
|
||||
var xml = Blockly.Xml.workspaceToDom(Mixly.Editor.blockEditor);
|
||||
data = Blockly.Xml.domToText(xml);
|
||||
type = 'xml';
|
||||
$.post('../../post_server_js.php', { unionid: id, hash: hash, userid: userid, content: data, type: type }, function (result) {
|
||||
var json = eval('(' + result + ')');
|
||||
alert(json.result);
|
||||
});
|
||||
}
|
||||
|
||||
Lms.loadfrommoodle = function() {
|
||||
// 当有localStorage缓存时,不从api接口中读取数据,否则api读取后会存在localStorage中,重复显示出来 add by qiang 20180521
|
||||
var xml_str = LocalStorage.get(BOARD.boardType);
|
||||
var pattern = /<xml[\w\W]*?>(.*)<\/xml>/i
|
||||
var code = pattern.exec(xml_str)
|
||||
|
||||
if (code != null && code[1] != '') {
|
||||
console.log(code[1]);
|
||||
console.log('read from localStorage');
|
||||
return false;
|
||||
}
|
||||
var data = '';
|
||||
var type = 'xml';
|
||||
var id = Lms.getUrlParam('id');
|
||||
var hash = Lms.getUrlParam('hash');
|
||||
var userid = Lms.getUrlParam('userid');
|
||||
var taskid = Lms.getUrlParam('taskid');
|
||||
if (id == null || hash == null || userid == null) {
|
||||
// alert('参数有误,请检查');
|
||||
return false;
|
||||
}
|
||||
$.post('../../get_content_microbitpy.php', { unionid: id, hash: hash, userid: userid, content: data }, function (result) {
|
||||
const { blockEditor } = Editor;
|
||||
if (result == '') {
|
||||
return;
|
||||
} else {
|
||||
var count = blockEditor.getAllBlocks().length;
|
||||
if (count) {
|
||||
blockEditor.clear();
|
||||
}
|
||||
type = result.substr(0, 3);
|
||||
data = result.substr(3);
|
||||
}
|
||||
File.parseData(`.${type}`, data);
|
||||
var selectFile = document.getElementById('select_file');
|
||||
if (selectFile != null) {
|
||||
$("#select_file").remove();
|
||||
$("#select_file_wrapper").remove();
|
||||
selectFile = document.getElementById('select_file');
|
||||
}
|
||||
if (selectFile == null) {
|
||||
var selectFileDom = document.createElement('INPUT');
|
||||
selectFileDom.type = 'file';
|
||||
selectFileDom.id = 'select_file';
|
||||
|
||||
var selectFileWrapperDom = document.createElement('DIV');
|
||||
selectFileWrapperDom.id = 'select_file_wrapper';
|
||||
selectFileWrapperDom.style.display = 'none';
|
||||
selectFileWrapperDom.appendChild(selectFileDom);
|
||||
|
||||
document.body.appendChild(selectFileWrapperDom);
|
||||
selectFile = document.getElementById('select_file');
|
||||
}
|
||||
selectFile.click();
|
||||
});
|
||||
}
|
||||
|
||||
Lms.save2hex = function() {
|
||||
const code = MFile.getCode();
|
||||
const output = MicrobitFs.getHex(code);
|
||||
var blob = new Blob([output], { type: 'text/xml' });
|
||||
saveAs(blob, 'blockduino.hex');
|
||||
}
|
||||
|
||||
Lms.changeState = function() {
|
||||
var id = Lms.getUrlParam('id');
|
||||
var hash = Lms.getUrlParam('hash');
|
||||
var userid = Lms.getUrlParam('userid');
|
||||
var taskid = Lms.getUrlParam('taskid');
|
||||
if (id == null || hash == null || userid == null) {
|
||||
return false;
|
||||
}
|
||||
const $dom = $(DOM_STR);
|
||||
$dom.find('a').off().click(() => {
|
||||
Lms.save2moodle();
|
||||
})
|
||||
$('#nav #nav-right-btn-list').append($dom);
|
||||
Lms.loadfrommoodle();
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
281
common/modules/mixly-modules/web/serial.js
Normal file
281
common/modules/mixly-modules/web/serial.js
Normal file
@@ -0,0 +1,281 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('Mixly.Serial');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.Nav');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.Debug');
|
||||
goog.require('Mixly.Registry');
|
||||
goog.require('Mixly.Web');
|
||||
goog.provide('Mixly.Web.Serial');
|
||||
|
||||
const {
|
||||
Serial,
|
||||
Env,
|
||||
Nav,
|
||||
Msg,
|
||||
Debug,
|
||||
Registry,
|
||||
Web
|
||||
} = Mixly;
|
||||
|
||||
|
||||
class WebSerial extends Serial {
|
||||
static {
|
||||
this.portToNameRegistry = new Registry();
|
||||
this.nameToPortRegistry = new Registry();
|
||||
|
||||
this.getConfig = function () {
|
||||
return Serial.getConfig();
|
||||
}
|
||||
|
||||
this.getSelectedPortName = function () {
|
||||
return Serial.getSelectedPortName();
|
||||
}
|
||||
|
||||
this.getCurrentPortsName = function () {
|
||||
return Serial.getCurrentPortsName();
|
||||
}
|
||||
|
||||
this.refreshPorts = function () {
|
||||
let portsName = [];
|
||||
for (let name of this.nameToPortRegistry.keys()) {
|
||||
portsName.push({ name });
|
||||
}
|
||||
Serial.renderSelectBox(portsName);
|
||||
}
|
||||
|
||||
this.requestPort = function () {
|
||||
navigator.serial.requestPort()
|
||||
.then((serialport) => {
|
||||
this.addPort(serialport);
|
||||
this.refreshPorts();
|
||||
})
|
||||
.catch(Debug.error);
|
||||
}
|
||||
|
||||
this.getPort = function (name) {
|
||||
return this.nameToPortRegistry.getItem(name);
|
||||
}
|
||||
|
||||
this.addPort = function (serialport) {
|
||||
if (this.portToNameRegistry.hasKey(serialport)) {
|
||||
return;
|
||||
}
|
||||
let name = '';
|
||||
for (let i = 1; i <= 20; i++) {
|
||||
name = `serial${i}`;
|
||||
if (this.nameToPortRegistry.hasKey(name)) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.portToNameRegistry.register(serialport, name);
|
||||
this.nameToPortRegistry.register(name, serialport);
|
||||
}
|
||||
|
||||
this.removePort = function (serialport) {
|
||||
if (!this.portToNameRegistry.hasKey(serialport)) {
|
||||
return;
|
||||
}
|
||||
const name = this.portToNameRegistry.getItem(serialport);
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
this.portToNameRegistry.unregister(serialport);
|
||||
this.nameToPortRegistry.unregister(name);
|
||||
}
|
||||
|
||||
this.addEventsListener = function () {
|
||||
navigator.serial.addEventListener('connect', (event) => {
|
||||
this.addPort(event.target);
|
||||
this.refreshPorts();
|
||||
});
|
||||
|
||||
navigator.serial.addEventListener('disconnect', (event) => {
|
||||
this.removePort(event.target);
|
||||
this.refreshPorts();
|
||||
});
|
||||
}
|
||||
navigator.serial.getPorts().then((serialports) => {
|
||||
for (let serialport of serialports) {
|
||||
this.addPort(serialport);
|
||||
}
|
||||
});
|
||||
this.addEventsListener();
|
||||
}
|
||||
|
||||
#serialport_ = null;
|
||||
#keepReading_ = null;
|
||||
#reader_ = null;
|
||||
#writer_ = null;
|
||||
#stringTemp_ = '';
|
||||
constructor(port) {
|
||||
super(port);
|
||||
}
|
||||
|
||||
#addEventsListener_() {
|
||||
this.#addReadEventListener_();
|
||||
}
|
||||
|
||||
async #addReadEventListener_() {
|
||||
const { readable } = this.#serialport_;
|
||||
while (readable && this.#keepReading_) {
|
||||
this.#reader_ = readable.getReader();
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await this.#reader_.read();
|
||||
value && this.onBuffer(value);
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.#keepReading_ = false;
|
||||
Debug.error(error);
|
||||
} finally {
|
||||
this.#reader_ && this.#reader_.releaseLock();
|
||||
await this.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async open(baud) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const portsName = Serial.getCurrentPortsName();
|
||||
const currentPortName = this.getPortName();
|
||||
if (!portsName.includes(currentPortName)) {
|
||||
reject('无可用串口');
|
||||
return;
|
||||
}
|
||||
if (this.isOpened()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
baud = baud ?? this.getBaudRate();
|
||||
this.#serialport_ = WebSerial.getPort(currentPortName);
|
||||
this.#serialport_.open({ baudRate: baud })
|
||||
.then(() => {
|
||||
super.open(baud);
|
||||
super.setBaudRate(baud);
|
||||
this.#keepReading_ = true;
|
||||
this.onOpen();
|
||||
this.#addEventsListener_();
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
async #waitForUnlock_(timeout) {
|
||||
while (
|
||||
(this.#serialport_.readable && this.#serialport_.readable.locked) ||
|
||||
(this.#serialport_.writable && this.#serialport_.writable.locked)
|
||||
) {
|
||||
await this.sleep(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (!this.isOpened()) {
|
||||
return;
|
||||
}
|
||||
super.close();
|
||||
if (this.#serialport_.readable?.locked) {
|
||||
this.#keepReading_ = false;
|
||||
await this.#reader_?.cancel();
|
||||
}
|
||||
await this.#waitForUnlock_(400);
|
||||
this.#reader_ = undefined;
|
||||
await this.#serialport_.close();
|
||||
this.#stringTemp_ = '';
|
||||
this.onClose(1);
|
||||
}
|
||||
|
||||
async setBaudRate(baud) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isOpened() || this.getBaudRate() === baud) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.close()
|
||||
.then(() => this.open(baud))
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
async sendString(str) {
|
||||
const buffer = this.encode(str);
|
||||
return this.sendBuffer(buffer);
|
||||
}
|
||||
|
||||
async sendBuffer(buffer) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const { writable } = this.#serialport_;
|
||||
const writer = writable.getWriter();
|
||||
if (!(buffer instanceof Uint8Array)) {
|
||||
buffer = new Uint8Array(buffer);
|
||||
}
|
||||
writer.write(buffer)
|
||||
.then(() => {
|
||||
writer.releaseLock();
|
||||
resolve();
|
||||
})
|
||||
.catch(() => {
|
||||
writer.releaseLock();
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async setDTRAndRTS(dtr, rts) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.isOpened()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
this.#serialport_.setSignals({
|
||||
dataTerminalReady: dtr,
|
||||
requestToSend: rts
|
||||
})
|
||||
.then(() => {
|
||||
super.setDTRAndRTS(dtr, rts);
|
||||
resolve();
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
async setDTR(dtr) {
|
||||
return this.setDTRAndRTS(dtr, this.getRTS());
|
||||
}
|
||||
|
||||
async setRTS(rts) {
|
||||
return this.setDTRAndRTS(this.getDTR(), rts);
|
||||
}
|
||||
|
||||
onBuffer(buffer) {
|
||||
super.onBuffer(buffer);
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
super.onByte(buffer[i]);
|
||||
}
|
||||
const string = this.decodeBuffer(buffer);
|
||||
if (!string) {
|
||||
return;
|
||||
}
|
||||
for (let char of string) {
|
||||
super.onChar(char);
|
||||
if (['\r', '\n'].includes(char)) {
|
||||
super.onString(this.#stringTemp_);
|
||||
this.#stringTemp_ = '';
|
||||
} else {
|
||||
this.#stringTemp_ += char;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Web.Serial = WebSerial;
|
||||
|
||||
});
|
||||
285
common/modules/mixly-modules/web/serialport.js
Normal file
285
common/modules/mixly-modules/web/serialport.js
Normal file
@@ -0,0 +1,285 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('Mixly.MString');
|
||||
goog.require('Mixly.Web');
|
||||
goog.provide('Mixly.Web.SerialPort');
|
||||
|
||||
const { MString, Web } = Mixly;
|
||||
|
||||
const { SerialPort } = Web;
|
||||
|
||||
SerialPort.output = [];
|
||||
SerialPort.inputBuffer = [];
|
||||
SerialPort.outputBuffer = [];
|
||||
SerialPort.refreshInputBuffer = false;
|
||||
SerialPort.refreshOutputBuffer = true;
|
||||
SerialPort.obj = null;
|
||||
SerialPort.onDataLine = null;
|
||||
SerialPort.keepReading = false;
|
||||
|
||||
SerialPort.encoder = new TextEncoder('utf8');
|
||||
SerialPort.decoder = new TextDecoder('utf8');
|
||||
SerialPort.dtr = false;
|
||||
SerialPort.rts = false;
|
||||
SerialPort.name = 'serialport';
|
||||
|
||||
SerialPort.connect = (baud = 115200, onDataLine = (message) => {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (SerialPort.isConnected()) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
navigator.serial.requestPort()
|
||||
.then((device) => {
|
||||
SerialPort.obj = device;
|
||||
return device.open({ baudRate: baud });
|
||||
})
|
||||
.then(() => {
|
||||
SerialPort.keepReading = true;
|
||||
SerialPort.onDataLine = onDataLine;
|
||||
SerialPort.addReadEvent(onDataLine);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
SerialPort.obj = null;
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
SerialPort.close = async () => {
|
||||
if (SerialPort.isConnected()) {
|
||||
SerialPort.keepReading = false;
|
||||
if (!SerialPort.isConnected()) {
|
||||
return;
|
||||
}
|
||||
const serialObj = SerialPort.obj;
|
||||
if (serialObj.readable && serialObj.readable.locked) {
|
||||
try {
|
||||
await SerialPort.reader.cancel();
|
||||
SerialPort.reader.releaseLock();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
if (serialObj.writable && serialObj.writable.locked) {
|
||||
try {
|
||||
SerialPort.writer.releaseLock();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await serialObj.close();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
SerialPort.obj = null;
|
||||
}
|
||||
}
|
||||
|
||||
SerialPort.isConnected = () => {
|
||||
return SerialPort.obj ? true : false;
|
||||
}
|
||||
|
||||
SerialPort.readLine = () => {
|
||||
var text = "", ch = '';
|
||||
var endWithLF = false;
|
||||
let i = 0;
|
||||
do {
|
||||
ch = SerialPort.readChar();
|
||||
if (ch.length) {
|
||||
if (ch === '\n') {
|
||||
endWithLF = true;
|
||||
} else {
|
||||
text += ch;
|
||||
}
|
||||
}
|
||||
} while (ch.length && !endWithLF)
|
||||
return { text: text, endWithLF: endWithLF };
|
||||
}
|
||||
|
||||
SerialPort.readChar = () => {
|
||||
var readBuf = [];
|
||||
var buffLength = 0;
|
||||
var text = "";
|
||||
const len = SerialPort.outputBuffer.length;
|
||||
/* UTF-8编码方式
|
||||
* ------------------------------------------------------------
|
||||
* |1字节 0xxxxxxx |
|
||||
* |2字节 110xxxxx 10xxxxxx |
|
||||
* |3字节 1110xxxx 10xxxxxx 10xxxxxx |
|
||||
* |4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
|
||||
* |5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
|
||||
* |6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx|
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
for (var i = 0; i < len; i++) {
|
||||
const data = SerialPort.outputBuffer.shift();
|
||||
if ((data & 0x80) == 0x00) {
|
||||
text = String.fromCharCode(data);
|
||||
break;
|
||||
} else if ((data & 0xc0) == 0x80) {
|
||||
readBuf.push(data);
|
||||
if (readBuf.length >= buffLength) {
|
||||
text = SerialPort.decoder.decode(new Uint8Array(readBuf));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
let dataNum = data & 0xe0;
|
||||
switch (dataNum) {
|
||||
case 0xfc:
|
||||
buffLength = 6;
|
||||
break;
|
||||
case 0xf8:
|
||||
buffLength = 5;
|
||||
break;
|
||||
case 0xf0:
|
||||
buffLength = 4;
|
||||
break;
|
||||
case 0xe0:
|
||||
buffLength = 3;
|
||||
break;
|
||||
case 0xc0:
|
||||
default:
|
||||
buffLength = 2;
|
||||
}
|
||||
readBuf.push(data);
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
SerialPort.startReadLine = (onDataLine = (message) => {}) => {
|
||||
SerialPort.readLineTimer = window.setTimeout(() => {
|
||||
if (!SerialPort.keepReading) {
|
||||
window.clearTimeout(SerialPort.readLineTimer);
|
||||
return;
|
||||
}
|
||||
let endWithLF = false;
|
||||
do {
|
||||
const readObj = SerialPort.readLine();
|
||||
endWithLF = readObj.endWithLF;
|
||||
const { text } = readObj;
|
||||
SerialPort.output.push((SerialPort.output.length? SerialPort.output.pop() : '') + text);
|
||||
if (endWithLF) {
|
||||
const len = SerialPort.output.length;
|
||||
SerialPort.output[len - 1] = MString.decode(SerialPort.output[len - 1]);
|
||||
if (len) {
|
||||
onDataLine(SerialPort.output[len - 1]);
|
||||
}
|
||||
SerialPort.output.push('');
|
||||
}
|
||||
} while (endWithLF);
|
||||
while (SerialPort.output.length > 500) {
|
||||
SerialPort.output.shift();
|
||||
}
|
||||
if (SerialPort.keepReading) {
|
||||
SerialPort.startReadLine(onDataLine);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
SerialPort.addReadEvent = async (onDataLine = (message) => {}) => {
|
||||
SerialPort.output = [];
|
||||
SerialPort.inputBuffer = [];
|
||||
SerialPort.outputBuffer = [];
|
||||
SerialPort.refreshInputBuffer = false;
|
||||
SerialPort.refreshOutputBuffer = true;
|
||||
SerialPort.startReadLine(onDataLine);
|
||||
while (SerialPort.obj.readable && SerialPort.keepReading) {
|
||||
SerialPort.reader = SerialPort.obj.readable.getReader();
|
||||
try {
|
||||
while (true) {
|
||||
const { value, done } = await SerialPort.reader.read();
|
||||
if (SerialPort.refreshOutputBuffer && value) {
|
||||
SerialPort.outputBuffer = [ ...SerialPort.outputBuffer, ...value ];
|
||||
}
|
||||
if (SerialPort.refreshInputBuffer && value) {
|
||||
SerialPort.inputBuffer = [ ...SerialPort.inputBuffer, ...value ];
|
||||
}
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
SerialPort.reader.releaseLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SerialPort.AddOnConnectEvent = (onConnect) => {
|
||||
navigator.serial.addEventListener('connect', (event) => {
|
||||
onConnect();
|
||||
});
|
||||
}
|
||||
|
||||
SerialPort.AddOnDisconnectEvent = (onDisconnect) => {
|
||||
navigator.serial.addEventListener('disconnect', (event) => {
|
||||
SerialPort.obj && SerialPort.close();
|
||||
onDisconnect();
|
||||
});
|
||||
}
|
||||
|
||||
SerialPort.writeString = async (str) => {
|
||||
const buffer = SerialPort.encoder.encode(str);
|
||||
await SerialPort.writeByteArr(buffer);
|
||||
}
|
||||
|
||||
SerialPort.writeByteArr = async (buffer) => {
|
||||
const writer = SerialPort.obj.writable.getWriter();
|
||||
await writer.write(new Int8Array(buffer).buffer);
|
||||
writer.releaseLock();
|
||||
await SerialPort.sleep(200);
|
||||
}
|
||||
|
||||
SerialPort.writeCtrlA = async () => {
|
||||
await SerialPort.writeByteArr([1, 13, 10]);
|
||||
}
|
||||
|
||||
SerialPort.writeCtrlB = async () => {
|
||||
await SerialPort.writeByteArr([2, 13, 10]);
|
||||
}
|
||||
|
||||
SerialPort.writeCtrlC = async () => {
|
||||
await SerialPort.writeByteArr([3, 13, 10]);
|
||||
}
|
||||
|
||||
SerialPort.writeCtrlD = async () => {
|
||||
await SerialPort.writeByteArr([3, 4]);
|
||||
}
|
||||
|
||||
SerialPort.setBaudRate = async (baud) => {
|
||||
SerialPort.keepReading = false;
|
||||
const serialObj = SerialPort.obj;
|
||||
await SerialPort.close();
|
||||
await serialObj.open({ baudRate: baud - 0 });
|
||||
SerialPort.obj = serialObj;
|
||||
SerialPort.keepReading = true;
|
||||
SerialPort.setSignals(SerialPort.dtr, SerialPort.rts);
|
||||
SerialPort.addReadEvent(SerialPort.onDataLine);
|
||||
}
|
||||
|
||||
SerialPort.setDTR = async (value) => {
|
||||
SerialPort.dtr = value;
|
||||
await SerialPort.obj.setSignals({ dataTerminalReady: value });
|
||||
}
|
||||
|
||||
SerialPort.setRTS = async (value) => {
|
||||
SerialPort.rts = value;
|
||||
await SerialPort.obj.setSignals({ requestToSend: value });
|
||||
}
|
||||
|
||||
SerialPort.setSignals = async (dtr, rts) => {
|
||||
SerialPort.dtr = dtr;
|
||||
SerialPort.rts = rts;
|
||||
await SerialPort.obj.setSignals({ dataTerminalReady: dtr, requestToSend: rts });
|
||||
}
|
||||
|
||||
SerialPort.sleep = (ms) => {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
});
|
||||
229
common/modules/mixly-modules/web/usb.js
Normal file
229
common/modules/mixly-modules/web/usb.js
Normal file
@@ -0,0 +1,229 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('DAPjs');
|
||||
goog.require('Mixly.Serial');
|
||||
goog.require('Mixly.Env');
|
||||
goog.require('Mixly.Nav');
|
||||
goog.require('Mixly.Msg');
|
||||
goog.require('Mixly.Debug');
|
||||
goog.require('Mixly.Registry');
|
||||
goog.require('Mixly.Web');
|
||||
goog.provide('Mixly.Web.USB');
|
||||
|
||||
const {
|
||||
Serial,
|
||||
Env,
|
||||
Nav,
|
||||
Msg,
|
||||
Debug,
|
||||
Registry,
|
||||
Web
|
||||
} = Mixly;
|
||||
|
||||
class USB extends Serial {
|
||||
static {
|
||||
this.portToNameRegistry = new Registry();
|
||||
this.serialNumberToNameRegistry = new Registry();
|
||||
this.nameToPortRegistry = new Registry();
|
||||
|
||||
this.getConfig = function () {
|
||||
return Serial.getConfig();
|
||||
}
|
||||
|
||||
this.getSelectedPortName = function () {
|
||||
return Serial.getSelectedPortName();
|
||||
}
|
||||
|
||||
this.getCurrentPortsName = function () {
|
||||
return Serial.getCurrentPortsName();
|
||||
}
|
||||
|
||||
this.refreshPorts = function () {
|
||||
let portsName = [];
|
||||
for (let name of this.nameToPortRegistry.keys()) {
|
||||
portsName.push({ name });
|
||||
}
|
||||
Serial.renderSelectBox(portsName);
|
||||
}
|
||||
|
||||
this.requestPort = function () {
|
||||
navigator.usb.requestDevice({ filters: [{ vendorId: 0xD28 }] })
|
||||
.then((device) => {
|
||||
this.addPort(device);
|
||||
this.refreshPorts();
|
||||
})
|
||||
.catch(Debug.error);
|
||||
}
|
||||
|
||||
this.getPort = function (name) {
|
||||
return this.nameToPortRegistry.getItem(name);
|
||||
}
|
||||
|
||||
this.addPort = function (device) {
|
||||
if (this.portToNameRegistry.hasKey(device)) {
|
||||
return;
|
||||
}
|
||||
const { serialNumber } = device;
|
||||
let name = this.serialNumberToNameRegistry.getItem(serialNumber);
|
||||
if (!name) {
|
||||
for (let i = 1; i <= 20; i++) {
|
||||
name = `usb${i}`;
|
||||
if (this.nameToPortRegistry.hasKey(name)) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.serialNumberToNameRegistry.register(serialNumber, name);
|
||||
}
|
||||
this.portToNameRegistry.register(device, name);
|
||||
this.nameToPortRegistry.register(name, device);
|
||||
}
|
||||
|
||||
this.removePort = function (device) {
|
||||
if (!this.portToNameRegistry.hasKey(device)) {
|
||||
return;
|
||||
}
|
||||
const name = this.portToNameRegistry.getItem(device);
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
this.portToNameRegistry.unregister(device);
|
||||
this.nameToPortRegistry.unregister(name);
|
||||
}
|
||||
|
||||
this.addEventsListener = function () {
|
||||
navigator.usb.addEventListener('connect', (event) => {
|
||||
this.addPort(event.device);
|
||||
this.refreshPorts();
|
||||
});
|
||||
|
||||
navigator.usb.addEventListener('disconnect', (event) => {
|
||||
this.removePort(event.device);
|
||||
this.refreshPorts();
|
||||
});
|
||||
}
|
||||
navigator.usb.getDevices().then((devices) => {
|
||||
for (let device of devices) {
|
||||
this.addPort(device);
|
||||
}
|
||||
});
|
||||
this.addEventsListener();
|
||||
}
|
||||
|
||||
#device_ = null;
|
||||
#webUSB_ = null;
|
||||
#dapLink_ = null;
|
||||
#keepReading_ = null;
|
||||
#reader_ = null;
|
||||
#writer_ = null;
|
||||
#stringTemp_ = '';
|
||||
constructor(port) {
|
||||
super(port);
|
||||
}
|
||||
|
||||
#addEventsListener_() {
|
||||
this.#addReadEventListener_();
|
||||
}
|
||||
|
||||
#addReadEventListener_() {
|
||||
this.#dapLink_.on(DAPjs.DAPLink.EVENT_SERIAL_DATA, data => {
|
||||
const str = data.split('');
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
this.onChar(str[i]);
|
||||
}
|
||||
});
|
||||
this.#dapLink_.startSerialRead(this.#device_);
|
||||
}
|
||||
|
||||
async open(baud) {
|
||||
const portsName = Serial.getCurrentPortsName();
|
||||
const currentPortName = this.getPortName();
|
||||
if (!portsName.includes(currentPortName)) {
|
||||
throw new Error('无可用串口');
|
||||
}
|
||||
if (this.isOpened()) {
|
||||
return;
|
||||
}
|
||||
baud = baud ?? this.getBaudRate();
|
||||
this.#device_ = USB.getPort(currentPortName);
|
||||
this.#webUSB_ = new DAPjs.WebUSB(this.#device_);
|
||||
this.#dapLink_ = new DAPjs.DAPLink(this.#webUSB_);
|
||||
await this.#dapLink_.connect();
|
||||
super.open(baud);
|
||||
await this.setBaudRate(baud);
|
||||
this.onOpen();
|
||||
this.#addEventsListener_();
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (!this.isOpened()) {
|
||||
return;
|
||||
}
|
||||
super.close();
|
||||
this.#dapLink_.removeAllListeners(DAPjs.DAPLink.EVENT_SERIAL_DATA);
|
||||
this.#dapLink_.stopSerialRead();
|
||||
await this.#dapLink_.stopSerialRead();
|
||||
await this.#dapLink_.disconnect();
|
||||
this.#dapLink_ = null;
|
||||
await this.#webUSB_.close();
|
||||
this.#webUSB_ = null;
|
||||
await this.#device_.close();
|
||||
this.#device_ = null;
|
||||
this.onClose(1);
|
||||
}
|
||||
|
||||
async setBaudRate(baud) {
|
||||
if (!this.isOpened() || this.getBaudRate() === baud) {
|
||||
return;
|
||||
}
|
||||
await this.setSerialBaudrate(baud);
|
||||
await super.setBaudRate(baud);
|
||||
}
|
||||
|
||||
async sendString(str) {
|
||||
return this.#dapLink_.serialWrite(str);
|
||||
}
|
||||
|
||||
async sendBuffer(buffer) {
|
||||
if (typeof buffer.unshift === 'function') {
|
||||
buffer.unshift(buffer.length);
|
||||
buffer = new Uint8Array(buffer).buffer;
|
||||
}
|
||||
return this.#dapLink_.send(132, buffer);
|
||||
}
|
||||
|
||||
async setDTRAndRTS(dtr, rts) {
|
||||
if (!this.isOpened()
|
||||
|| (this.getDTR() === dtr && this.getRTS() === rts)) {
|
||||
return;
|
||||
}
|
||||
await super.setDTRAndRTS(dtr, rts);
|
||||
}
|
||||
|
||||
async setDTR(dtr) {
|
||||
return this.setDTRAndRTS(dtr, this.getRTS());
|
||||
}
|
||||
|
||||
async setRTS(rts) {
|
||||
return this.setDTRAndRTS(this.getDTR(), rts);
|
||||
}
|
||||
|
||||
onChar(char) {
|
||||
super.onChar(char);
|
||||
if (['\r', '\n'].includes(char)) {
|
||||
super.onString(this.#stringTemp_);
|
||||
this.#stringTemp_ = '';
|
||||
} else {
|
||||
this.#stringTemp_ += char;
|
||||
}
|
||||
const buffer = this.encode(char);
|
||||
super.onBuffer(buffer);
|
||||
for (let i = 0; i < buffer.length; i++) {
|
||||
super.onByte(buffer[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Web.USB = USB;
|
||||
|
||||
});
|
||||
6
common/modules/mixly-modules/web/web.js
Normal file
6
common/modules/mixly-modules/web/web.js
Normal file
@@ -0,0 +1,6 @@
|
||||
goog.loadJs('web', () => {
|
||||
|
||||
goog.require('Mixly');
|
||||
goog.provide('Mixly.Web');
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user