Update(boards): 更新micropython microbit和mithoncc板卡

This commit is contained in:
王立帮
2024-12-20 20:39:14 +08:00
parent 8727b62cb2
commit fe0a581e2c
24 changed files with 721 additions and 63287 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -142,14 +142,6 @@ div.blocklyToolboxDiv > div.blocklyToolboxContents > div:nth-child(18) > div.blo
background:url('../../../../common/media/mark/net2.png') no-repeat;
background-size: 100% auto;
}
div.blocklyToolboxDiv > div.blocklyToolboxContents > div:nth-child(19) > div.blocklyTreeRow > div.blocklyTreeRowContentContainer > span.blocklyTreeIcon{
background:url('../../../../common/media/mark/factory3.png') no-repeat;
background-size: 100% auto;
}
div.blocklyToolboxDiv > div.blocklyToolboxContents > div:nth-child(19) > div.blocklyTreeRow.blocklyTreeSelected > div.blocklyTreeRowContentContainer > span.blocklyTreeIcon{
background:url('../../../../common/media/mark/factory4.png') no-repeat;
background-size: 100% auto;
}
#catCommunicate.blocklyTreeRow > div.blocklyTreeRowContentContainer > span.blocklyTreeIcon{
background:url('../../../../common/media/mark/requests.png') no-repeat;
background-size: 100% auto;
@@ -157,4 +149,12 @@ div.blocklyToolboxDiv > div.blocklyToolboxContents > div:nth-child(19) > div.blo
#catCommunicate.blocklyTreeRow.blocklyTreeSelected > div.blocklyTreeRowContentContainer > span.blocklyTreeIcon{
background:url('../../../../common/media/mark/requests2.png') no-repeat;
background-size: 100% auto;
}
#catFactory.blocklyTreeRow > div.blocklyTreeRowContentContainer > span.blocklyTreeIcon{
background:url('../../../../common/media/mark/factory3.png') no-repeat;
background-size: 100% auto;
}
#catFactory.blocklyTreeRow.blocklyTreeSelected > div.blocklyTreeRowContentContainer > span.blocklyTreeIcon{
background:url('../../../../common/media/mark/factory4.png') no-repeat;
background-size: 100% auto;
}

View File

@@ -33,6 +33,13 @@ import {
Python
} from '@mixly/python';
import {
MicroPythonNetworkBlocks,
MicroPythonSensorOnBoardBlocks,
MicroPythonNetworkGenerators,
MicroPythonSensorOnBoardGenerators
} from '@mixly/micropython';
import {
MicropythonNRF51822MicrobitPins,
MicropythonNRF51822MicrobitActuatorBlocks,
@@ -81,6 +88,8 @@ Object.assign(
PythonSetBlocks,
PythonHtmlBlocks,
PythonUtilityBlocks,
MicroPythonNetworkBlocks,
MicroPythonSensorOnBoardBlocks,
MicropythonNRF51822MicrobitActuatorBlocks,
MicropythonNRF51822MicrobitCommunicateBlocks,
MicropythonNRF51822MicrobitDisplayBlocks,
@@ -107,6 +116,8 @@ Object.assign(
PythonSetGenerators,
PythonHtmlGenerators,
PythonUtilityGenerators,
MicroPythonNetworkGenerators,
MicroPythonSensorOnBoardGenerators,
MicropythonNRF51822MicrobitActuatorGenerators,
MicropythonNRF51822MicrobitCommunicateGenerators,
MicropythonNRF51822MicrobitDisplayGenerators,

View File

@@ -1872,7 +1872,7 @@
<block type="variables_set">
<field name="VAR">response</field>
<value name="VALUE">
<block type="requests_get2">
<block type="requests_get2">
<value name="URL">
<shadow type="text">
<field name="TEXT">http://mixio.mixly.cn</field>
@@ -1881,10 +1881,10 @@
</block>
</value>
</block>
<block type="variables_set">
<block type="variables_set">
<field name="VAR">response</field>
<value name="VALUE">
<block type="requests_post">
<block type="requests_post">
<value name="URL">
<shadow type="text">
<field name="TEXT">http://mixio.mixly.cn</field>

View File

@@ -5,7 +5,8 @@ const { merge } = require("webpack-merge");
module.exports = merge(common, {
resolve: {
alias: {
'@mixly/python': path.resolve(__dirname, '../python')
'@mixly/python': path.resolve(__dirname, '../python'),
'@mixly/micropython': path.resolve(__dirname, '../micropython')
}
}
});

View File

@@ -33,6 +33,11 @@ import {
Python
} from '@mixly/python';
import {
MicroPythonNetworkBlocks,
MicroPythonNetworkGenerators
} from '@mixly/micropython';
import {
MicropythonNRF51822MithonCCPins,
MicropythonNRF51822MithonCCActuatorBlocks,
@@ -83,6 +88,7 @@ Object.assign(
PythonSetBlocks,
PythonHtmlBlocks,
PythonUtilityBlocks,
MicroPythonNetworkBlocks,
MicropythonNRF51822MithonCCActuatorBlocks,
MicropythonNRF51822MithonCCCommunicateBlocks,
MicropythonNRF51822MithonCCDisplayBlocks,
@@ -111,6 +117,7 @@ Object.assign(
PythonSetGenerators,
PythonHtmlGenerators,
PythonUtilityGenerators,
MicroPythonNetworkGenerators,
MicropythonNRF51822MithonCCActuatorGenerators,
MicropythonNRF51822MithonCCCommunicateGenerators,
MicropythonNRF51822MithonCCDisplayGenerators,

View File

@@ -1253,7 +1253,7 @@
<block type="variables_set">
<field name="VAR">response</field>
<value name="VALUE">
<block type="requests_get2">
<block type="requests_get2">
<value name="URL">
<shadow type="text">
<field name="TEXT">http://mixio.mixly.cn</field>
@@ -1262,10 +1262,10 @@
</block>
</value>
</block>
<block type="variables_set">
<block type="variables_set">
<field name="VAR">response</field>
<value name="VALUE">
<block type="requests_post">
<block type="requests_post">
<value name="URL">
<shadow type="text">
<field name="TEXT">http://mixio.mixly.cn</field>

View File

@@ -5,7 +5,8 @@ const { merge } = require("webpack-merge");
module.exports = merge(common, {
resolve: {
alias: {
'@mixly/python': path.resolve(__dirname, '../python')
'@mixly/python': path.resolve(__dirname, '../python'),
'@mixly/micropython': path.resolve(__dirname, '../micropython')
}
}
});

View File

@@ -239,9 +239,6 @@ class App extends Component {
id: 'command-burn-btn',
displayText: Msg.Lang['nav.btn.burn'],
preconditionFn: () => {
if (!goog.isElectron && BOARD.web.com === 'hid') {
return false;
}
return SELECTED_BOARD?.nav?.burn;
},
callback: () => BU.initBurn(),

View File

@@ -562,7 +562,7 @@ BU.uploadByUSB = async (portName) => {
return;
}
}
const statusBarSerial = mainStatusBarTabs.getStatusBarById(portName);
let statusBarSerial = mainStatusBarTabs.getStatusBarById(portName);
if (statusBarSerial) {
await statusBarSerial.close();
}
@@ -570,17 +570,19 @@ BU.uploadByUSB = async (portName) => {
const port = Serial.getPort(portName);
const statusBarTerminal = mainStatusBarTabs.getStatusBarById('output');
const dapWrapper = new DAPWrapper(port, {
event: (data) => {
console.log(data);
},
event: () => {},
log: () => {}
});
const partialFlashing = new PartialFlashing(dapWrapper, {
event: (data) => {
console.log(data);
}
event: () => {}
});
let boardId = 0x9901;
const boardKey = Boards.getSelectedBoardKey();
if (boardKey === 'micropython:nrf51822:v2') {
boardId = 0x9903;
}
BU.burning = false;
BU.uploading = true;
statusBarTerminal.setValue(Msg.Lang['shell.uploading'] + '...\n');
@@ -590,6 +592,12 @@ BU.uploadByUSB = async (portName) => {
const editor = mainWorkspace.getEditorsManager().getActive();
const code = editor.getCode();
FSWrapper.writeFile('main.py', code);
const importsMap = BU.getImportModules(code);
for (let key in importsMap) {
const filename = importsMap[key]['__name__'];
const data = goog.get(importsMap[key]['__path__']);
FSWrapper.writeFile(filename, data);
}
layer.open({
type: 1,
title: Msg.Lang['shell.uploading'] + '...',
@@ -599,7 +607,19 @@ BU.uploadByUSB = async (portName) => {
closeBtn: 0,
success: async function (layero, index) {
try {
await partialFlashing.flashAsync(new BoardId(0x9900), FSWrapper, () => {});
let prevPercent = 0;
await partialFlashing.flashAsync(new BoardId(0x9900), FSWrapper, 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.close(index);
layer.msg(Msg.Lang['shell.uploadSucc'], { time: 1000 });
statusBarTerminal.addValue(`==${Msg.Lang['shell.uploadSucc']}==\n`);

View File

@@ -0,0 +1,418 @@
goog.loadJs('web', () => {
goog.require('Mixly.Web');
goog.provide('Mixly.Web.HIDTransport');
const { Web } = Mixly;
class Transport {
slipReaderEnabled = false;
baudrate = 0;
traceLog = "";
lastTraceTime = Date.now();
finished = false;
buffer = new Uint8Array(0);
device = null;
tracing = false;
received = new Uint8Array(0);
constructor(device, tracing, enableSlipReader) {
this.slipReaderEnabled = enableSlipReader;
this.device = device;
this.tracing = tracing;
}
async sendBuffer(buffer) {
for (let i = 0; i < buffer.length / 31; i++) {
const len = Math.min((i + 1) * 31, buffer.length) - i * 31;
let temp = new Uint8Array(len + 1);
temp[0] = len;
temp.set(buffer.subarray(i * 31, Math.min((i + 1) * 31, buffer.length)), 1);
await this.device.sendReport(0, temp);
}
// if (buffer instanceof Uint8Array) {
// let temp = new Uint8Array(buffer.length + 1);
// temp[0] = buffer.length;
// temp.set(buffer, 1);
// buffer = temp;
// } else {
// buffer.unshift(buffer.length);
// buffer = new Uint8Array(buffer);
// }
}
#addEventsListener_() {
this.device.oninputreport = (event) => {
const { data } = event;
const length = Math.min(data.getUint8(0), data.byteLength);
let buffer = [];
for (let i = 1; i <= length; i++) {
buffer.push(data.getUint8(i));
}
this.received = this.appendArray(this.received, new Uint8Array(buffer));
};
// this.device.onclose = () => {}
}
async readOrigin(timeout) {
let time = Date.now();
let output = {
done: false,
value: null
};
while (Date.now() - time < timeout && !this.finished) {
if (this.received.byteLength) {
output.value = this.received;
this.received = new Uint8Array(0);
break;
} else {
await this.sleep(5);
}
}
if (!output.value) {
output.value = new Uint8Array(0);
}
output.done = this.finished;
return output;
}
/**
* Request the serial device vendor ID and Product ID as string.
* @returns {string} Return the device VendorID and ProductID from SerialPortInfo as formatted string.
*/
getInfo() {
const { vendorId, productId } = this.device;
return vendorId && productId
? `WebHID VendorID 0x${vendorId.toString(16)} ProductID 0x${productId.toString(16)}`
: "";
}
/**
* Request the serial device product id from SerialPortInfo.
* @returns {number | undefined} Return the product ID.
*/
getPid() {
return this.device.productId;
}
/**
* Format received or sent data for tracing output.
* @param {string} message Message to format as trace line.
*/
trace(message) {
if (!this.tracing) {
return;
}
const delta = Date.now() - this.lastTraceTime;
const prefix = `TRACE ${delta.toFixed(3)}`;
const traceMessage = `${prefix} ${message}`;
console.log(traceMessage);
this.traceLog += traceMessage + "\n";
}
async returnTrace() {
try {
await navigator.clipboard.writeText(this.traceLog);
console.log("Text copied to clipboard!");
} catch (err) {
console.error("Failed to copy text:", err);
}
}
hexify(s) {
return Array.from(s)
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("")
.padEnd(16, " ");
}
hexConvert(uint8Array, autoSplit) {
if (autoSplit && uint8Array.length > 16) {
let result = "";
let s = uint8Array;
while (s.length > 0) {
const line = s.slice(0, 16);
const asciiLine = String.fromCharCode(...line)
.split("")
.map((c) => (c === " " || (c >= " " && c <= "~" && c !== " ") ? c : "."))
.join("");
s = s.slice(16);
result += `\n ${this.hexify(line.slice(0, 8))} ${this.hexify(line.slice(8))} | ${asciiLine}`;
}
return result;
} else {
return this.hexify(uint8Array);
}
}
/**
* Format data packet using the Serial Line Internet Protocol (SLIP).
* @param {Uint8Array} data Binary unsigned 8 bit array data to format.
* @returns {Uint8Array} Formatted unsigned 8 bit data array.
*/
slipWriter(data) {
const outData = [];
outData.push(0xc0);
for (let i = 0; i < data.length; i++) {
if (data[i] === 0xdb) {
outData.push(0xdb, 0xdd);
} else if (data[i] === 0xc0) {
outData.push(0xdb, 0xdc);
} else {
outData.push(data[i]);
}
}
outData.push(0xc0);
return new Uint8Array(outData);
}
/**
* Write binary data to device using the WebSerial device writable stream.
* @param {Uint8Array} data 8 bit unsigned data array to write to device.
*/
async write(data) {
const outData = this.slipWriter(data);
if (this.tracing) {
console.log("Write bytes");
this.trace(`Write ${outData.length} bytes: ${this.hexConvert(outData)}`);
}
await this.sendBuffer(outData);
}
/**
* Append a buffer array after another buffer array
* @param {Uint8Array} arr1 - First array buffer.
* @param {Uint8Array} arr2 - magic hex number to select ROM.
* @returns {Uint8Array} Return a 8 bit unsigned array.
*/
appendArray(arr1, arr2) {
const combined = new Uint8Array(arr1.length + arr2.length);
combined.set(arr1);
combined.set(arr2, arr1.length);
return combined;
}
// Asynchronous generator to yield incoming data chunks
async *readLoop(timeout) {
try {
while (true) {
// Await the race between the timeout and the reader read
const result = await this.readOrigin(timeout);
// If a timeout occurs, result will be null; otherwise, it will have { value, done }
if (result === null) break;
const { value, done } = result;
if (done || !value) break;
yield value; // Yield each data chunk
}
} catch (error) {
console.error("Error reading from serial port:", error);
} finally {
this.buffer = new Uint8Array(0);
}
}
// Read a specific number of bytes
async newRead(numBytes, timeout) {
if (this.buffer.length >= numBytes) {
const output = this.buffer.slice(0, numBytes);
this.buffer = this.buffer.slice(numBytes); // Remove the returned data from buffer
return output;
}
while (this.buffer.length < numBytes) {
const readLoop = this.readLoop(timeout);
const { value, done } = await readLoop.next();
if (done || !value) {
break;
}
// Append the newly read data to the buffer
this.buffer = this.appendArray(this.buffer, value);
}
// Return as much data as possible
const output = this.buffer.slice(0, numBytes);
this.buffer = this.buffer.slice(numBytes);
return output;
}
async flushInput() {}
async flushOutput() {
this.buffer = new Uint8Array(0);
}
// `inWaiting` returns the count of bytes in the buffer
inWaiting() {
return this.buffer.length;
}
/**
* Detect if the data read from device is a Fatal or Guru meditation error.
* @param {Uint8Array} input Data read from device
*/
detectPanicHandler(input) {
const guruMeditationRegex = /G?uru Meditation Error: (?:Core \d panic'ed \(([a-zA-Z ]*)\))?/;
const fatalExceptionRegex = /F?atal exception \(\d+\): (?:([a-zA-Z ]*)?.*epc)?/;
const inputString = new TextDecoder("utf-8").decode(input);
const match = inputString.match(guruMeditationRegex) || inputString.match(fatalExceptionRegex);
if (match) {
const cause = match[1] || match[2];
const msg = `Guru Meditation Error detected${cause ? ` (${cause})` : ""}`;
throw new Error(msg);
}
}
SLIP_END = 0xc0;
SLIP_ESC = 0xdb;
SLIP_ESC_END = 0xdc;
SLIP_ESC_ESC = 0xdd;
/**
* Take a data array and return the first well formed packet after
* replacing the escape sequence. Reads at least 8 bytes.
* @param {number} timeout Timeout read data.
* @yields {Uint8Array} Formatted packet using SLIP escape sequences.
*/
async *read(timeout) {
let partialPacket = null;
let isEscaping = false;
let successfulSlip = false;
while (true) {
const waitingBytes = this.inWaiting();
const readBytes = await this.newRead(waitingBytes > 0 ? waitingBytes : 1, timeout);
if (!readBytes || readBytes.length === 0) {
const msg =
partialPacket === null
? successfulSlip
? "Serial data stream stopped: Possible serial noise or corruption."
: "No serial data received."
: `Packet content transfer stopped`;
this.trace(msg);
throw new Error(msg);
}
this.trace(`Read ${readBytes.length} bytes: ${this.hexConvert(readBytes)}`);
let i = 0; // Track position in readBytes
while (i < readBytes.length) {
const byte = readBytes[i++];
if (partialPacket === null) {
if (byte === this.SLIP_END) {
partialPacket = new Uint8Array(0); // Start of a new packet
} else {
this.trace(`Read invalid data: ${this.hexConvert(readBytes)}`);
const remainingData = await this.newRead(this.inWaiting(), timeout);
this.trace(`Remaining data in serial buffer: ${this.hexConvert(remainingData)}`);
this.detectPanicHandler(new Uint8Array([...readBytes, ...(remainingData || [])]));
throw new Error(`Invalid head of packet (0x${byte.toString(16)}): Possible serial noise or corruption.`);
}
} else if (isEscaping) {
isEscaping = false;
if (byte === this.SLIP_ESC_END) {
partialPacket = this.appendArray(partialPacket, new Uint8Array([this.SLIP_END]));
} else if (byte === this.SLIP_ESC_ESC) {
partialPacket = this.appendArray(partialPacket, new Uint8Array([this.SLIP_ESC]));
} else {
this.trace(`Read invalid data: ${this.hexConvert(readBytes)}`);
const remainingData = await this.newRead(this.inWaiting(), timeout);
this.trace(`Remaining data in serial buffer: ${this.hexConvert(remainingData)}`);
this.detectPanicHandler(new Uint8Array([...readBytes, ...(remainingData || [])]));
throw new Error(`Invalid SLIP escape (0xdb, 0x${byte.toString(16)})`);
}
} else if (byte === this.SLIP_ESC) {
isEscaping = true;
} else if (byte === this.SLIP_END) {
this.trace(`Received full packet: ${this.hexConvert(partialPacket)}`);
this.buffer = this.appendArray(this.buffer, readBytes.slice(i));
yield partialPacket;
partialPacket = null;
successfulSlip = true;
} else {
partialPacket = this.appendArray(partialPacket, new Uint8Array([byte]));
}
}
}
}
_DTR_state = false;
/**
* Send the RequestToSend (RTS) signal to given state
* # True for EN=LOW, chip in reset and False EN=HIGH, chip out of reset
* @param {boolean} state Boolean state to set the signal
*/
async setRTS(state) {
// # Work-around for adapters on Windows using the usbser.sys driver:
// # generate a dummy change to DTR so that the set-control-line-state
// # request is sent with the updated RTS state and the same DTR state
// Referenced to esptool.py
await this.setDTR(this._DTR_state);
}
/**
* Send the dataTerminalReady (DTS) signal to given state
* # True for IO0=LOW, chip in reset and False IO0=HIGH
* @param {boolean} state Boolean state to set the signal
*/
async setDTR(state) {
this._DTR_state = state;
}
/**
* Connect to serial device using the Webserial open method.
* @param {number} baud Number baud rate for serial connection. Default is 115200.
* @param {typeof import("w3c-web-serial").SerialOptions} serialOptions Serial Options for WebUSB SerialPort class.
*/
async connect(baud = 115200, serialOptions = {}) {
// await this.device.open({
// baudRate: baud,
// dataBits: serialOptions?.dataBits,
// stopBits: serialOptions?.stopBits,
// bufferSize: serialOptions?.bufferSize,
// parity: serialOptions?.parity,
// flowControl: serialOptions?.flowControl,
// });
await this.device.open();
this.finished = false;
this.#addEventsListener_();
this.baudrate = baud;
}
async sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Wait for a given timeout ms for serial device unlock.
* @param {number} timeout Timeout time in milliseconds (ms) to sleep
*/
async waitForUnlock(timeout) {}
/**
* Disconnect from serial device by running SerialPort.close() after streams unlock.
*/
async disconnect() {
await this.waitForUnlock(400);
this.finished = true;
this.device.oninputreport = null;
this.device.onclose = null;
await this.device.close();
}
}
Web.HIDTransport = Transport;
});

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -77,6 +77,12 @@ const CortexSpecialReg = {
// Many more.
};
const CoreRegister = {
SP: 13,
LR: 14,
PC: 15,
};
// Returns a representation of an Access Port Register.
// Drawn from https://github.com/mmoskal/dapjs/blob/a32f11f54e9e76a9c61896ddd425c1cb1a29c143/src/util.ts#L63
const apReg = (r, mode) => {
@@ -159,6 +165,40 @@ class DAPWrapper {
);
}
async connectAsync() {
await this.daplink.connect();
await this.daplink.setSerialBaudrate(115200);
await this.cortexM.connect();
this.logging.event({
type: "WebUSB-info",
message: "connected",
});
const serialInfo = this.boardSerialInfo();
this.log(`Detected board ID ${serialInfo.id}`);
if (
!this.loggedBoardSerialInfo ||
!this.loggedBoardSerialInfo.eq(this.boardSerialInfo())
) {
this.loggedBoardSerialInfo = this.boardSerialInfo();
this.logging.event({
type: "WebUSB-info",
message: "board-id/" + this.boardSerialInfo().id,
});
this.logging.event({
type: "WebUSB-info",
message:
"board-family-hic/" +
this.boardSerialInfo().familyId +
this.boardSerialInfo().hic,
});
}
this._pageSize = await this.cortexM.readMem32(FICR.CODEPAGESIZE);
this._numPages = await this.cortexM.readMem32(FICR.CODESIZE);
}
// Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L119
async reconnectAsync() {
if (this.initialConnectionComplete) {

View File

@@ -55,6 +55,21 @@ const CoreRegister = {
PC: 15,
};
/**
* Utility to time out an action after a delay.
*
* The action cannot be cancelled; it may still proceed after the timeout.
*/
async function withTimeout(actionPromise, timeout) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error());
}, timeout);
});
// timeoutPromise never resolves so result must be from action
return Promise.race([actionPromise, timeoutPromise]);
}
class Page {
constructor(targetAddr, data) {
this.targetAddr = targetAddr;
@@ -312,10 +327,10 @@ class PartialFlashing {
const data = fs.getIntelHexForBoardId(boardId.normalize().id.toString(16));
await this.dapwrapper.transport.open();
await this.dapwrapper.daplink.flash(data.buffer);
console.log({
type: "WebUSB-info",
message: "full-flash-successful",
})
// console.log({
// type: "WebUSB-info",
// message: "full-flash-successful",
// });
} finally {
this.dapwrapper.daplink.removeListener(
DAPjs.DAPLink.EVENT_PROGRESS,
@@ -353,10 +368,10 @@ class PartialFlashing {
if (e instanceof Error) {
this.log("Resetting micro:bit timed out");
this.log("Partial flashing failed. Attempting full flash");
console.log({
type: "WebUSB-info",
message: "flash-failed/attempting-full-flash",
});
// console.log({
// type: "WebUSB-info",
// message: "flash-failed/attempting-full-flash",
// });
await this.fullFlashAsync(boardId, fs, updateProgress);
return false;
} else {