Update: 更新 Web USB

This commit is contained in:
王立帮
2024-12-20 18:51:04 +08:00
parent 03211cb1b2
commit 8727b62cb2
17 changed files with 1428 additions and 583 deletions

View File

@@ -8,7 +8,6 @@ goog.require('Mixly.MArray');
goog.require('Mixly.Boards');
goog.require('Mixly.XML');
goog.require('Mixly.LayerExt');
goog.require('Mixly.MicrobitFs');
goog.require('Mixly.Msg');
goog.provide('Mixly.MFile');
@@ -20,7 +19,6 @@ const {
Boards,
XML,
LayerExt,
MicrobitFs,
Msg,
MFile
} = Mixly;
@@ -108,15 +106,6 @@ MFile.getCode = (type) => {
}
}
MFile.getHex = () => {
const code = MFile.getCode();
return MicrobitFs.getHex(code);
}
MFile.loadHex = (hexStr) => {
MicrobitFs.loadHex('main.py', hexStr);
}
MFile.getMix = () => {
const mixDom = $(Blockly.Xml.workspaceToDom(Editor.blockEditor)),
version = SOFTWARE?.version ?? 'Mixly 2.0',

View File

@@ -1,151 +0,0 @@
/**
* Wrapper for microbit-fs and microbit-universal-hex to perform filesystem
* operations into two hex files.
* https://github.com/microbit-foundation/microbit-fs
* https://github.com/microbit-foundation/microbit-universal-hex
*/
goog.loadJs('common', () => {
'use strict';
goog.require('microbitFs');
goog.provide('fsWrapper');
/**
* @returns An object with the fs wrapper.
*/
var uPyFs = null;
var commonFsSize = 20 * 1024;
var passthroughMethods = [
'create',
'exists',
'getStorageRemaining',
'getStorageSize',
'getStorageUsed',
'getUniversalHex',
'ls',
'read',
'readBytes',
'remove',
'size',
'write',
];
/**
* Duplicates some of the methods from the MicropythonFsHex class by
* creating functions with the same name in this object.
*/
function duplicateMethods() {
passthroughMethods.forEach(function(method) {
fsWrapper[method] = function() {
return uPyFs[method].apply(uPyFs, arguments);
};
});
}
/**
* Fetches both MicroPython hexes and sets up the file system with the
* initial main.py
*/
fsWrapper.setupFilesystem = function() {
var uPyV1 = null;
var uPyV2 = null;
var deferred1 = $.get('../common/micropython/microbit-micropython-v1.hex', function(fileStr) {
uPyV1 = fileStr;
}).fail(function() {
console.error('Could not load the MicroPython v1 file.');
});
var deferred2 = $.get('../common/micropython/microbit-micropython-v2.hex', function(fileStr) {
uPyV2 = fileStr;
}).fail(function() {
console.error('Could not load the MicroPython v2 file.');
});
return $.when(deferred1, deferred2).done(function() {
if (!uPyV1 || !uPyV2) {
console.error('There was an issue loading the MicroPython Hex files.');
}
// TODO: We need to use ID 9901 for app compatibility, but can soon be changed to 9900 (as per spec)
uPyFs = new microbitFs.MicropythonFsHex([
{ hex: uPyV1, boardId: 0x9901 },
{ hex: uPyV2, boardId: 0x9903 },
], {
'maxFsSize': commonFsSize,
});
duplicateMethods();
});
};
/**
* @param {string} boardId String with the Board ID for the generation.
* @returns Uint8Array with the data for the given Board ID.
*/
fsWrapper.getBytesForBoardId = function(boardId) {
if (boardId == '9900' || boardId == '9901') {
return uPyFs.getIntelHexBytes(0x9901);
} else if (boardId == '9903' || boardId == '9904') {
return uPyFs.getIntelHexBytes(0x9903);
} else {
throw Error('Could not recognise the Board ID ' + boardId);
}
};
/**
* @param {string} boardId String with the Board ID for the generation.
* @returns ArrayBuffer with the Intel Hex data for the given Board ID.
*/
fsWrapper.getIntelHexForBoardId = function(boardId) {
if (boardId == '9900' || boardId == '9901') {
var hexStr = uPyFs.getIntelHex(0x9901);
} else if (boardId == '9903' || boardId == '9904') {
var hexStr = uPyFs.getIntelHex(0x9903);
} else {
throw Error('Could not recognise the Board ID ' + boardId);
}
// iHex is ASCII so we can do a 1-to-1 conversion from chars to bytes
var hexBuffer = new Uint8Array(hexStr.length);
for (var i = 0, strLen = hexStr.length; i < strLen; i++) {
hexBuffer[i] = hexStr.charCodeAt(i);
}
return hexBuffer.buffer;
};
/**
* Import the files from the provide hex string into the filesystem.
* If the import is successful this deletes all the previous files.
*
* @param {string} hexStr Hex (Intel or Universal) string with files to
* import.
* @return {string[]} Array with the filenames of all files imported.
*/
fsWrapper.importHexFiles = function(hexStr) {
var filesNames = uPyFs.importFilesFromHex(hexStr, {
overwrite: true,
formatFirst: true
});
if (!filesNames.length) {
throw new Error('The filesystem in the hex file was empty');
}
return filesNames;
};
/**
* Import an appended script from the provide hex string into the filesystem.
* If the import is successful this deletes all the previous files.
*
* @param {string} hexStr Hex (Intel or Universal) string with files to
* import.
* @return {string[]} Array with the filenames of all files imported.
*/
fsWrapper.importHexAppended = function(hexStr) {
var code = microbitFs.getIntelHexAppendedScript(hexStr);
if (!code) {
throw new Error('No appended code found in the hex file');
};
uPyFs.ls().forEach(function(fileName) {
uPyFs.remove(fileName);
});
uPyFs.write('main.py', code);
return ['main.py'];
};
});

View File

@@ -1,137 +0,0 @@
goog.loadJs('common', () => {
goog.require('fsWrapper');
goog.require('Mixly.Config');
goog.provide('Mixly.MicrobitFs');
const {
Config,
MicrobitFs
} = Mixly;
const { BOARD } = Config;
const { nav = {} } = BOARD;
MicrobitFs.init = () => {
fsWrapper.setupFilesystem()
.then(() => {
console.log('初始化成功');
})
.fail(() => {
console.log('初始化失败');
});
}
if (!nav.compile && nav.upload && nav.save?.hex)
MicrobitFs.init();
// Reset the filesystem and load the files from this hex file to the fsWrapper and editor
MicrobitFs.loadHex = (filename, hexStr) => {
var importedFiles = [];
// If hexStr is parsed correctly it formats the file system before adding the new files
try {
importedFiles = fsWrapper.importHexFiles(hexStr);
} catch (hexImportError) {
try {
importedFiles = fsWrapper.importHexAppended(hexStr);
} catch (appendedError) {
console.log(hexImportError.message);
}
}
// Check if imported files includes a main.py file
var code = '';
if (importedFiles.indexOf(filename) > -1) {
code = fsWrapper.read(filename);
} else {
alert('no ' + filename);
}
Editor.mainEditor.drag.full('NEGATIVE'); // 完全显示代码编辑器
Editor.codeEditor.setValue(code, -1);
}
// Function for adding file to filesystem
MicrobitFs.loadFileToFilesystem = (filename, fileBytes) => {
// For main.py confirm if the user wants to replace the editor content
if (filename === 'main.py') {
return;
}
try {
if (fsWrapper.exists(filename)) {
fsWrapper.remove(filename);
fsWrapper.create(filename, fileBytes);
} else {
fsWrapper.write(filename, fileBytes);
}
// Check if the filesystem has run out of space
var _ = fsWrapper.getUniversalHex();
} catch (e) {
if (fsWrapper.exists(filename)) {
fsWrapper.remove(filename);
}
return alert(filename + '\n' + e.message);
}
}
MicrobitFs.updateMainPy = (code) => {
try {
// Remove main.py if editor content is empty to download a hex file
// with MicroPython included (also includes the rest of the filesystem)
if (fsWrapper.exists('main.py')) {
fsWrapper.remove('main.py');
}
for (var i = 0; i < py_module.length; i++) {
if (fsWrapper.exists(py_module[i]['filename'])) {
fsWrapper.remove(py_module[i]['filename']);
}
}
if (code) {
fsWrapper.create('main.py', code);
}
var str = code;
var arrayObj = new Array();
str.trim().split("\n").forEach(function (v, i) {
arrayObj.push(v);
});
let moduleName = "";
for (var i = 0; i < arrayObj.length; i++) {
if (arrayObj[i].indexOf("from") == 0) {
moduleName = arrayObj[i].substring(4, arrayObj[i].indexOf("import"));
moduleName = moduleName.replace(/(^\s*)|(\s*$)/g, "");
if (fsWrapper.exists(moduleName + '.py'))
continue;
for (var j = 0; j < py_module.length; j++) {
if (py_module[j]['filename'] == moduleName + ".py") {
MicrobitFs.loadFileToFilesystem(py_module[j]['filename'], py_module[j]['code']);
}
}
} else if (arrayObj[i].indexOf("import") == 0) {
moduleName = arrayObj[i].substring(6);
moduleName = moduleName.replace(/(^\s*)|(\s*$)/g, "");
if (fsWrapper.exists(moduleName + '.py'))
continue;
for (var j = 0; j < py_module.length; j++) {
if (py_module[j]['filename'] == moduleName + ".py") {
MicrobitFs.loadFileToFilesystem(py_module[j]['filename'], py_module[j]['code']);
}
}
}
}
} catch (e) {
// We generate a user readable error here to be caught and displayed
throw new Error(e.message);
}
}
MicrobitFs.getHex = (code) => {
try {
MicrobitFs.updateMainPy(code);
return output = fsWrapper.getUniversalHex();
} catch (e) {
alert(e.message);
return null;
}
}
});

View File

@@ -186,7 +186,8 @@ class StatusBarsManager extends PagesManager {
}
});*/
if (['micropython', 'circuitpython'].includes(BOARD.language.toLowerCase())) {
if (['micropython', 'circuitpython'].includes(BOARD.language.toLowerCase())
&& !['BBC micro:bit', 'Mithon CC'].includes(BOARD.boardType)) {
menu.add({
weight: 2,
type: 'sep1',

View File

@@ -684,32 +684,12 @@
"Mixly.Boards",
"Mixly.XML",
"Mixly.LayerExt",
"Mixly.MicrobitFs",
"Mixly.Msg"
],
"provide": [
"Mixly.MFile"
]
},
{
"path": "/common/microbit-fs-wrapper.js",
"require": [
"microbitFs"
],
"provide": [
"fsWrapper"
]
},
{
"path": "/common/microbit-fs.js",
"require": [
"fsWrapper",
"Mixly.Config"
],
"provide": [
"Mixly.MicrobitFs"
]
},
{
"path": "/common/mixly.js",
"require": [],
@@ -1558,6 +1538,9 @@
"path": "/web/burn-upload.js",
"require": [
"path",
"FSWrapper",
"DAPWrapper",
"PartialFlashing",
"ESPTool",
"AdafruitESPTool",
"CryptoJS",
@@ -1639,22 +1622,6 @@
"Mixly.Web.HID"
]
},
{
"path": "/web/lms.js",
"require": [
"saveAs",
"Blob",
"Blockly",
"Mixly.MFile",
"Mixly.Config",
"Mixly.MicrobitFs",
"Mixly.LocalStorage",
"Mixly.Web.File"
],
"provide": [
"Mixly.Web.Lms"
]
},
{
"path": "/web/serial.js",
"require": [

View File

@@ -1,6 +1,10 @@
goog.loadJs('web', () => {
goog.require('path');
goog.require('BoardId');
goog.require('FSWrapper');
goog.require('DAPWrapper');
goog.require('PartialFlashing');
goog.require('ESPTool');
goog.require('AdafruitESPTool');
goog.require('CryptoJS');
@@ -58,6 +62,10 @@ BU.FILMWARE_LAYER = new HTMLTemplate(
const BAUD = goog.platform() === 'darwin' ? 460800 : 921600;
if (['BBC micro:bit', 'Mithon CC'].includes(BOARD.boardType)) {
FSWrapper.setupFilesystem(path.join(Env.boardDirPath, 'build'));
}
BU.requestPort = async () => {
await Serial.requestPort();
}
@@ -136,85 +144,93 @@ BU.initBurn = () => {
}
}
BU.burnByUSB = () => {
const portName = 'web-usb';
Serial.connect(portName, 115200, async (port) => {
if (!port) {
return;
}
let portObj = Serial.portsOperator[portName];
const { toolConfig, serialport } = portObj;
const prevBaud = toolConfig.baudRates;
if (prevBaud !== 115200) {
toolConfig.baudRates = 115200;
await serialport.setBaudRate(toolConfig.baudRates);
}
const { web } = SELECTED_BOARD;
const { burn } = web;
const hexStr = goog.get(path.join(Env.boardDirPath, burn.filePath));
const hex2Blob = new Blob([ hexStr ], { type: 'text/plain' });
const buffer = await hex2Blob.arrayBuffer();
if (!buffer) {
layer.msg(Msg.Lang['shell.bin.readFailed'], { time: 1000 });
return;
}
BU.burning = true;
BU.uploading = false;
const { mainStatusBarTabs } = Mixly;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.setValue(Msg.Lang['shell.burning'] + '...\n');
mainStatusBarTabs.show();
mainStatusBarTabs.changeTo('output');
const layerNum = layer.open({
type: 1,
title: Msg.Lang['shell.burning'] + '...',
content: $('#mixly-loader-div'),
shade: LayerExt.SHADE_NAV,
resize: false,
closeBtn: 0,
success: function (layero, index) {
$(".layui-layer-page").css("z-index","198910151");
$("#mixly-loader-btn").hide();
let prevPercent = 0;
Serial.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`);
});
Serial.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);
}
Serial.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.burnByUSB = async () => {
const { mainStatusBarTabs } = Mixly;
let portName = Serial.getSelectedPortName();
if (!portName) {
try {
await BU.requestPort();
portName = Serial.getSelectedPortName();
if (!portName) {
return;
}
});
} catch (error) {
Debug.error(error);
return;
}
}
const statusBarSerial = mainStatusBarTabs.getStatusBarById(portName);
if (statusBarSerial) {
await statusBarSerial.close();
}
const { web } = SELECTED_BOARD;
const { burn } = web;
const hexStr = goog.get(path.join(Env.boardDirPath, burn.filePath));
const hex2Blob = new Blob([ hexStr ], { type: 'text/plain' });
const buffer = await hex2Blob.arrayBuffer();
if (!buffer) {
layer.msg(Msg.Lang['shell.bin.readFailed'], { time: 1000 });
return;
}
BU.burning = true;
BU.uploading = false;
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
statusBarTerminal.setValue(`${Msg.Lang['shell.burning']}...\n`);
mainStatusBarTabs.show();
mainStatusBarTabs.changeTo('output');
const port = Serial.getPort(portName);
const webUSB = new DAPjs.WebUSB(port);
const dapLink = new DAPjs.DAPLink(webUSB);
try {
await dapLink.connect();
await dapLink.setSerialBaudrate(115200);
} catch (error) {
Debug.error(error);
return;
}
let prevPercent = 0;
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`);
});
layer.open({
type: 1,
title: `${Msg.Lang['shell.burning']}...`,
content: $('#mixly-loader-div'),
shade: LayerExt.SHADE_NAV,
resize: false,
closeBtn: 0,
success: async (layero, index) => {
$('#mixly-loader-btn').hide();
try {
await dapLink.flash(buffer);
layer.close(index);
layer.msg(Msg.Lang['shell.burnSucc'], { time: 1000 });
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnSucc']}==\n`);
} catch (error) {
Debug.error(error);
layer.close(index);
statusBarTerminal.addValue(`==${Msg.Lang['shell.burnFailed']}==\n`);
} finally {
dapLink.removeAllListeners(DAPjs.DAPLink.EVENT_PROGRESS);
await dapLink.disconnect();
await webUSB.close();
await port.close();
}
},
end: function () {
$('#mixly-loader-btn').css('display', 'inline-block');
$('#mixly-loader-div').css('display', 'none');
}
});
}
@@ -299,7 +315,7 @@ BU.burnWithEsptool = async (binFile, erase) => {
compress: true,
calculateMD5Hash: (image) => CryptoJS.MD5(CryptoJS.enc.Latin1.parse(image))
};
const layerNum = layer.open({
layer.open({
type: 1,
title: Msg.Lang['shell.burning'] + '...',
content: $('#mixly-loader-div'),
@@ -410,7 +426,7 @@ BU.burnWithAdafruitEsptool = async (binFile, erase) => {
statusBarTerminal.addValue("Done!\n");
BU.burning = true;
BU.uploading = false;
const layerNum = layer.open({
layer.open({
type: 1,
title: Msg.Lang['shell.burning'] + '...',
content: $('#mixly-loader-div'),
@@ -525,7 +541,86 @@ BU.initUpload = async () => {
return;
}
}
BU.uploadWithAmpy(portName);
if (['BBC micro:bit', 'Mithon CC'].includes(BOARD.boardType)) {
BU.uploadByUSB(portName);
} else {
BU.uploadWithAmpy(portName);
}
}
BU.uploadByUSB = async (portName) => {
const { mainStatusBarTabs } = Mixly;
if (!portName) {
try {
await BU.requestPort();
portName = Serial.getSelectedPortName();
if (!portName) {
return;
}
} catch (error) {
Debug.error(error);
return;
}
}
const statusBarSerial = mainStatusBarTabs.getStatusBarById(portName);
if (statusBarSerial) {
await statusBarSerial.close();
}
const port = Serial.getPort(portName);
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
const dapWrapper = new DAPWrapper(port, {
event: (data) => {
console.log(data);
},
log: () => {}
});
const partialFlashing = new PartialFlashing(dapWrapper, {
event: (data) => {
console.log(data);
}
});
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 code = editor.getCode();
FSWrapper.writeFile('main.py', code);
layer.open({
type: 1,
title: Msg.Lang['shell.uploading'] + '...',
content: $('#mixly-loader-div'),
shade: LayerExt.SHADE_NAV,
resize: false,
closeBtn: 0,
success: async function (layero, index) {
try {
await partialFlashing.flashAsync(new BoardId(0x9900), FSWrapper, () => {});
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);
await statusBarSerial.open();
} catch (error) {
await dapWrapper.disconnectAsync();
layer.close(index);
console.error(error);
statusBarTerminal.addValue(`${error}\n`);
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadFailed']}==\n`);
}
BU.burning = false;
BU.uploading = false;
}
});
}
BU.uploadWithAmpy = (portName) => {
@@ -547,7 +642,7 @@ BU.uploadWithAmpy = (portName) => {
useBuffer = true;
dataLength = 30;
}
const layerNum = layer.open({
layer.open({
type: 1,
title: Msg.Lang['shell.uploading'] + '...',
content: $('#mixly-loader-div'),

View File

@@ -119,9 +119,9 @@ class WebHID extends Serial {
async #addReadEventListener_() {
this.#device_.oninputreport = (event) => {
const { data, reportId } = event;
const length = Math.min(data.getUint8(0), data.byteLength);
const length = Math.min(data.getUint8(0) + 1, data.byteLength);
let buffer = [];
for (let i = 1; i <= length; i++) {
for (let i = 1; i < length; i++) {
buffer.push(data.getUint8(i));
}
this.onBuffer(buffer);

View File

@@ -1,139 +0,0 @@
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

@@ -91,6 +91,7 @@ class USB extends Serial {
});
navigator?.usb?.addEventListener('disconnect', (event) => {
event.device.onclose && event.device.onclose();
this.removePort(event.device);
this.refreshPorts();
});
@@ -122,13 +123,29 @@ class USB extends Serial {
}
#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.#reader_ = this.#startSerialRead_();
this.#device_.onclose = () => {
if (!this.isOpened()) {
return;
}
});
this.#dapLink_.startSerialRead(this.#device_);
super.close();
this.#stringTemp_ = '';
this.onClose(1);
}
}
async #startSerialRead_(serialDelay = 10, autoConnect = false) {
this.#dapLink_.serialPolling = true;
while (this.#dapLink_.serialPolling) {
const data = await this.#dapLink_.serialRead();
if (data !== undefined) {
const numberArray = Array.prototype.slice.call(new Uint8Array(data));
this.onBuffer(numberArray);
}
await new Promise(resolve => setTimeout(resolve, serialDelay));
}
}
async open(baud) {
@@ -156,9 +173,10 @@ class USB extends Serial {
return;
}
super.close();
this.#dapLink_.removeAllListeners(DAPjs.DAPLink.EVENT_SERIAL_DATA);
this.#dapLink_.stopSerialRead();
await this.#dapLink_.stopSerialRead();
if (this.#reader_) {
await this.#reader_;
}
await this.#dapLink_.disconnect();
this.#dapLink_ = null;
await this.#webUSB_.close();
@@ -172,7 +190,7 @@ class USB extends Serial {
if (!this.isOpened() || this.getBaudRate() === baud) {
return;
}
await this.setSerialBaudrate(baud);
await this.#dapLink_.setSerialBaudrate(baud);
await super.setBaudRate(baud);
}
@@ -204,19 +222,24 @@ class USB extends Serial {
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);
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;
}
}
}
}