firmware/sagas: refactor firmwareFlashUsbDfu

This allows the firmwareFlashUsbDfu to take an arbitrary binary blob
instead of a Pybricks firmware.zip file. This will allow it to be used
to restore the official LEGO firmware as well.
This commit is contained in:
David Lechner
2022-11-18 19:28:00 -06:00
committed by David Lechner
parent 71c8605773
commit cf99ab223d
5 changed files with 54 additions and 64 deletions
+6 -6
View File
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2020-2022 The Pybricks Authors
import { FirmwareReaderError } from '@pybricks/firmware';
import { FirmwareReaderError, HubType } from '@pybricks/firmware';
import { createAction } from '../actions';
import { Hub } from '../components/hubPicker';
@@ -360,14 +360,14 @@ export const didFailToFinish = createAction(didFailToFinishCreator);
/**
* Low-level action to flash firmware using LEGO's DFU over USB.
* @param data The firmware zip file data.
* @param hubName A custom hub name or an empty string to use the default name.
* @param firmware The firmware binary blob.
* @param hubType The hub type the firmware blob is for.
*/
export const firmwareFlashUsbDfu = createAction(
(data: ArrayBuffer, hubName: string) => ({
(firmware: ArrayBuffer, hubType: HubType) => ({
type: 'firmware.action.flashUsbDfu',
data,
hubName,
firmware,
hubType,
}),
);
-21
View File
@@ -1,21 +0,0 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
import { Intent } from '@blueprintjs/core';
import React from 'react';
import type { CreateToast } from '../../toasterTypes';
import { useI18n } from './i18n';
const FirmwareMismatch: React.VoidFunctionComponent = () => {
const i18n = useI18n();
return <p>{i18n.translate('firmwareMismatch.message')}</p>;
};
export const firmwareMismatch: CreateToast = (onAction) => {
return {
message: <FirmwareMismatch />,
icon: 'error',
intent: Intent.DANGER,
onDismiss: () => onAction('dismiss'),
};
};
-2
View File
@@ -2,7 +2,6 @@
// Copyright (c) 2022 The Pybricks Authors
import { dfuError } from './DfuError';
import { firmwareMismatch } from './FirmwareMismatch';
import { flashProgress } from './FlashProgress';
import { noDfuHub } from './NoDfuHub';
import { noDfuInterface } from './NoDfuInterface';
@@ -11,7 +10,6 @@ import { releaseButton } from './ReleaseButton';
export default {
dfuError,
firmwareMismatch,
flashProgress,
noDfuHub,
noDfuInterface,
-3
View File
@@ -20,9 +20,6 @@
"noDfuInterface": {
"message": "This is very unusual. The USB device did not contain the expected interface."
},
"firmwareMismatch": {
"message": "Cannot flash firmware. The firmware file is for a different kind of hub."
},
"flashProgress": {
"erasing": "Erasing internal flash memory: {percent}",
"flashing": "Writing new firmware: {percent}"
+48 -32
View File
@@ -616,13 +616,6 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
}
}
/** Maps USB Product ID to LWP3 hub type ID */
const productIdMap: ReadonlyMap<LegoUsbProductId, HubType> = new Map([
[LegoUsbProductId.SpikePrimeBootloader, HubType.PrimeHub],
[LegoUsbProductId.SpikeEssentialBootloader, HubType.EssentialHub],
[LegoUsbProductId.MindstormsRobotInventorBootloader, HubType.PrimeHub],
]);
// currently all hubs use the same start address
const dfuFirmwareStartAddress = 0x08008000;
@@ -664,6 +657,31 @@ function* handleDfuWriteProcess(event: {
);
}
function getUsbDeviceFiltersForHub(hubType: HubType): USBDeviceFilter[] {
switch (hubType) {
case HubType.PrimeHub:
return [
{
vendorId: legoUsbVendorId,
productId: LegoUsbProductId.SpikePrimeBootloader,
},
{
vendorId: legoUsbVendorId,
productId: LegoUsbProductId.MindstormsRobotInventorBootloader,
},
];
case HubType.EssentialHub:
return [
{
vendorId: legoUsbVendorId,
productId: LegoUsbProductId.SpikeEssentialBootloader,
},
];
default:
throw new Error(`unsupported hub type: ${hubType}`);
}
}
function* handleFlashUsbDfu(action: ReturnType<typeof firmwareFlashUsbDfu>): Generator {
const defer = new Array<() => void>();
@@ -678,21 +696,7 @@ function* handleFlashUsbDfu(action: ReturnType<typeof firmwareFlashUsbDfu>): Gen
const device = yield* call(() =>
navigator.usb
.requestDevice({
filters: [
{
vendorId: legoUsbVendorId,
productId: LegoUsbProductId.SpikePrimeBootloader,
},
{
vendorId: legoUsbVendorId,
productId: LegoUsbProductId.SpikeEssentialBootloader,
},
{
vendorId: legoUsbVendorId,
productId:
LegoUsbProductId.MindstormsRobotInventorBootloader,
},
],
filters: getUsbDeviceFiltersForHub(action.hubType),
})
.catch((err) => {
if (err instanceof DOMException && err.name === 'NotFoundError') {
@@ -749,16 +753,8 @@ function* handleFlashUsbDfu(action: ReturnType<typeof firmwareFlashUsbDfu>): Gen
}),
);
const { firmware, deviceId } = yield* loadFirmware(action.data, action.hubName);
if (deviceId !== productIdMap.get(device.productId)) {
yield* put(alertsShowAlert('firmware', 'firmwareMismatch'));
yield* put(firmwareDidFailToFlashUsbDfu());
return;
}
dfu.dfuseStartAddress = dfuFirmwareStartAddress;
const writeProc = dfu.write(1024, firmware, true);
const writeProc = dfu.write(1024, action.firmware, true);
const eraseProcessChan = eventChannel<{
bytesSent: number;
@@ -817,6 +813,7 @@ function* handleFlashUsbDfu(action: ReturnType<typeof firmwareFlashUsbDfu>): Gen
// errors can happen, e.g. if the USB cable is disconnected while
// flashing the firmware
if (error) {
// istanbul ignore if
if (process.env.NODE_ENV !== 'test') {
console.error(error);
}
@@ -844,6 +841,7 @@ function* handleFlashUsbDfu(action: ReturnType<typeof firmwareFlashUsbDfu>): Gen
yield* put(firmwareDidFlashUsbDfu());
} catch (err) {
// istanbul ignore if
if (process.env.NODE_ENV !== 'test') {
console.error(err);
}
@@ -878,7 +876,25 @@ function* handleInstallPybricks(): Generator {
yield* put(flashFirmware(accepted.firmwareZip, accepted.hubName));
break;
case 'usb-lego-dfu':
yield* put(firmwareFlashUsbDfu(accepted.firmwareZip, accepted.hubName));
try {
const { firmware, deviceId } = yield* loadFirmware(
accepted.firmwareZip,
accepted.hubName,
);
yield* put(firmwareFlashUsbDfu(firmware, deviceId));
} catch (err) {
// istanbul ignore if
if (process.env.NODE_ENV !== 'test') {
console.error(err);
}
yield* put(
alertsShowAlert('alerts', 'unexpectedError', {
error: ensureError(err),
}),
);
}
break;
}
}