From cf99ab223d68260e0ac2b1f9f415618e3f5d7704 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Fri, 18 Nov 2022 16:40:24 -0600 Subject: [PATCH] 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. --- src/firmware/actions.ts | 12 ++-- src/firmware/alerts/FirmwareMismatch.tsx | 21 ------- src/firmware/alerts/index.ts | 2 - src/firmware/alerts/translations/en.json | 3 - src/firmware/sagas.ts | 80 ++++++++++++++---------- 5 files changed, 54 insertions(+), 64 deletions(-) delete mode 100644 src/firmware/alerts/FirmwareMismatch.tsx diff --git a/src/firmware/actions.ts b/src/firmware/actions.ts index 051a95e8..1e7dc03f 100644 --- a/src/firmware/actions.ts +++ b/src/firmware/actions.ts @@ -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, }), ); diff --git a/src/firmware/alerts/FirmwareMismatch.tsx b/src/firmware/alerts/FirmwareMismatch.tsx deleted file mode 100644 index dde81080..00000000 --- a/src/firmware/alerts/FirmwareMismatch.tsx +++ /dev/null @@ -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

{i18n.translate('firmwareMismatch.message')}

; -}; - -export const firmwareMismatch: CreateToast = (onAction) => { - return { - message: , - icon: 'error', - intent: Intent.DANGER, - onDismiss: () => onAction('dismiss'), - }; -}; diff --git a/src/firmware/alerts/index.ts b/src/firmware/alerts/index.ts index 2ae9b719..d25d162c 100644 --- a/src/firmware/alerts/index.ts +++ b/src/firmware/alerts/index.ts @@ -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, diff --git a/src/firmware/alerts/translations/en.json b/src/firmware/alerts/translations/en.json index d893bd5f..5784789e 100644 --- a/src/firmware/alerts/translations/en.json +++ b/src/firmware/alerts/translations/en.json @@ -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}" diff --git a/src/firmware/sagas.ts b/src/firmware/sagas.ts index f64bb5ea..da6b6d02 100644 --- a/src/firmware/sagas.ts +++ b/src/firmware/sagas.ts @@ -616,13 +616,6 @@ function* handleFlashFirmware(action: ReturnType): Generat } } -/** Maps USB Product ID to LWP3 hub type ID */ -const productIdMap: ReadonlyMap = 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): Generator { const defer = new Array<() => void>(); @@ -678,21 +696,7 @@ function* handleFlashUsbDfu(action: ReturnType): 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): 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): 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): 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; } }