初始化提交

This commit is contained in:
王立帮
2024-07-19 10:16:00 +08:00
parent 4c7b571f20
commit 4a2d56dcc4
7084 changed files with 741212 additions and 63 deletions

View 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;
});

View 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;
});

View 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));
}
});

View 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`);
});
}
});

View 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;
});

View 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 = () => {
}
});

View 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;
});

View 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();
}
});
}
});

View 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();
}
});

View 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;
});

View 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));
}
});

View 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;
});

View File

@@ -0,0 +1,6 @@
goog.loadJs('web', () => {
goog.require('Mixly');
goog.provide('Mixly.Web');
});