Spaces:
Runtime error
Runtime error
File size: 4,985 Bytes
8fd7a1d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
import {
isUniversalHex,
separateUniversalHex
} from '@microbit/microbit-universal-hex';
import {WebUSB, DAPLink} from 'dapjs';
import keyMirror from 'keymirror';
import log from './log.js';
import hexUrl from '../generated/microbit-hex-url.cjs';
/**
* @typedef {import('@microbit/microbit-universal-hex').IndividualHex} IndividualHex
*/
/**
* The version of a micro:bit.
* @enum {string}
*/
const DeviceVersion = keyMirror({
V1: null,
V2: null
});
const vendorId = 0x0d28;
const productId = 0x0204;
/**
* Assumes the device is a micro:bit and determines its version.
* @param {USBDevice} device The USB device to check.
* @returns {DeviceVersion} The version of the device.
* @throws {Error} If the device is not a recognized micro:bit.
*/
const getDeviceVersion = device => {
const microBitBoardId = device?.serialNumber?.substring(0, 4) ?? '';
switch (microBitBoardId) {
case '9900':
case '9901':
return DeviceVersion.V1;
case '9903':
case '9904':
case '9905':
case '9906':
return DeviceVersion.V2;
}
throw new Error('Could not identify micro:bit board version');
};
/**
* Checks micro:bit board version targetted by the hex file.
* @param {IndividualHex} hex The hex file to check.
* @returns {DeviceVersion} The version of the hex file.
* @throws {Error} If the hex file does not target a recognized micro:bit version.
*/
const getHexVersion = hex => {
switch (hex.boardId) {
case 0x9900:
case 0x9901:
return DeviceVersion.V1;
case 0x9903:
case 0x9904:
case 0x9905:
case 0x9906:
return DeviceVersion.V2;
}
throw new Error('Could not identify hex version');
};
/**
* Fetches the hex file and returns a map of micro:bit versions to hex file contents.
* @returns {Promise<Map<DeviceVersion, Uint8Array>>} A map of micro:bit versions to hex file contents.
* @throws {Error} If the fetch fails or cannot be interpreted as text.
* @throws {Error} If the hex file is not in universal format.
*/
const getHexMap = async () => {
const response = await fetch(hexUrl);
const hex = await response.text();
if (!isUniversalHex(hex)) {
throw new Error('Hex file must be in universal format');
}
const hexMap = new Map();
for (const hexObj of separateUniversalHex(hex)) {
const version = getHexVersion(hexObj);
const binary = new TextEncoder().encode(hex);
hexMap.set(version, binary);
}
return hexMap;
};
/**
* Copy the Scratch-specific hex file to the specified micro:bit.
* @param {USBDevice} device The micro:bit to update.
* @param {function(number): void} [progress] Optional function to call with progress updates in the range of [0..1].
* @returns {Promise<void>} A Promise that resolves when the update is completed.
* @throws {Error} If anything goes wrong while fetching the hex file or updating the micro:bit.
*/
const updateMicroBit = async (device, progress) => {
log.info(`Connecting to micro:bit`);
const transport = new WebUSB(device);
const target = new DAPLink(transport);
if (progress) {
target.on(DAPLink.EVENT_PROGRESS, progress);
}
log.info(`Checking micro:bit version`);
const version = getDeviceVersion(device);
log.info(`Collecting hex file`);
const hexMap = await getHexMap();
const hexData = hexMap.get(version);
if (!hexData) {
throw new Error(`Could not find hex file for micro:bit ${version}`);
}
log.info(`Connecting to micro:bit ${version}`);
await target.connect();
log.info(`Sending hex file...`);
try {
await target.flash(hexData);
} finally {
log.info('Disconnecting');
if (target.connected) {
await target.disconnect();
} else {
log.info('Already disconnected');
}
}
};
/**
* Requests a micro:bit from the browser then updates it with the Scratch-specific hex file.
* The browser is expected to prompt the user to select a micro:bit.
* @param {function(number): void} [progress] Optional function to call with progress updates in the range of [0..1].
* @returns {Promise<void>} A Promise that resolves when the update is completed.
* @throws {Error} If anything goes wrong while fetching the hex file or updating the micro:bit.
*/
const selectAndUpdateMicroBit = async progress => {
log.info('Selecting micro:bit');
const device = await navigator.usb.requestDevice({
filters: [{vendorId, productId}]
});
if (!device) {
log.info('No device selected');
return;
}
return updateMicroBit(device, progress);
};
/**
* Checks if the browser supports updating a micro:bit.
* @returns {boolean} True if the browser appears to support updating a micro:bit.
*/
const isMicroBitUpdateSupported = () =>
!!(navigator.usb && navigator.usb.requestDevice);
export {
isMicroBitUpdateSupported,
selectAndUpdateMicroBit
};
|