diff --git a/src/firmware/actions.ts b/src/firmware/actions.ts index af64e80f..8a4bc484 100644 --- a/src/firmware/actions.ts +++ b/src/firmware/actions.ts @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2025 The Pybricks Authors +// Copyright (c) 2020-2026 The Pybricks Authors import { FirmwareReaderError, HubType } from '@pybricks/firmware'; import { createAction } from '../actions'; @@ -139,15 +139,6 @@ export const didStart = createAction(() => ({ type: 'flashFirmware.action.didStart', })); -/** - * Action that indicates current firmware flashing progress. - * @param value The current progress (0 to 1). - */ -export const didProgress = createAction((value: number) => { - // assert(value >= 0 && value <= 1, 'value out of range'); - return { type: 'flashFirmware.action.didProgress', value }; -}); - /** Action that indicates that flashing firmware completed successfully. */ export const didFinish = createAction(() => ({ type: 'flashFirmware.action.didFinish', diff --git a/src/firmware/alerts/UnsupportedDfuHub.tsx b/src/firmware/alerts/UnsupportedDfuHub.tsx new file mode 100644 index 00000000..23e4a910 --- /dev/null +++ b/src/firmware/alerts/UnsupportedDfuHub.tsx @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2025-2026 The Pybricks Authors + +import { Intent } from '@blueprintjs/core'; +import { Error } from '@blueprintjs/icons'; +import React from 'react'; +import type { CreateToast } from '../../toasterTypes'; +import { useI18n } from './i18n'; + +const UnsupportedDfuHub: React.FunctionComponent = () => { + const i18n = useI18n(); + return ( + <> +

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

+ + ); +}; + +export const unsupportedDfuHub: CreateToast = (onAction) => ({ + message: , + icon: , + intent: Intent.DANGER, + onDismiss: () => onAction('dismiss'), +}); diff --git a/src/firmware/alerts/index.ts b/src/firmware/alerts/index.ts index a6187418..1d922f91 100644 --- a/src/firmware/alerts/index.ts +++ b/src/firmware/alerts/index.ts @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2022-2025 The Pybricks Authors +// Copyright (c) 2022-2026 The Pybricks Authors import { dfuError } from './DfuError'; import { flashProgress } from './FlashProgress'; @@ -8,6 +8,7 @@ import { noDfuInterface } from './NoDfuInterface'; import { noWebHid } from './NoWebHid'; import { noWebUsb } from './NoWebUsb'; import { releaseButton } from './ReleaseButton'; +import { unsupportedDfuHub } from './UnsupportedDfuHub'; export default { dfuError, @@ -17,4 +18,5 @@ export default { noWebHid, noWebUsb, releaseButton, + unsupportedDfuHub, }; diff --git a/src/firmware/alerts/translations/en.json b/src/firmware/alerts/translations/en.json index d43aa55d..d701b2b8 100644 --- a/src/firmware/alerts/translations/en.json +++ b/src/firmware/alerts/translations/en.json @@ -22,6 +22,9 @@ "installUsbDriverButton": "Install USB Driver", "configureUdevRulesButton": "Configure udev rules" }, + "unsupportedDfuHub": { + "message": "This hub has different internal electronics and requires a different firmware. Pybricks does not support this yet. We are working on it!" + }, "noDfuInterface": { "message": "This is very unusual. The USB device did not contain the expected interface." }, diff --git a/src/firmware/reducers.test.ts b/src/firmware/reducers.test.ts index 2a3ae568..1bbd527a 100644 --- a/src/firmware/reducers.test.ts +++ b/src/firmware/reducers.test.ts @@ -1,12 +1,11 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2021-2025 The Pybricks Authors +// Copyright (c) 2021-2026 The Pybricks Authors import { AnyAction } from 'redux'; import { FailToFinishReasonType, didFailToFinish, didFinish, - didProgress, didStart, } from './actions'; import reducers from './reducers'; @@ -26,7 +25,6 @@ test('initial state', () => { "isFirmwareFlashEV3InProgress": false, "isFirmwareFlashUsbDfuInProgress": false, "isFirmwareRestoreOfficialDfuInProgress": false, - "progress": null, "restoreOfficialDialog": { "isOpen": false, }, @@ -44,8 +42,3 @@ test('flashing', () => { ).flashing, ).toBe(false); }); - -test('progress', () => { - expect(reducers({ progress: 1 } as State, didStart()).progress).toBe(null); - expect(reducers({ progress: null } as State, didProgress(1)).progress).toBe(1); -}); diff --git a/src/firmware/reducers.ts b/src/firmware/reducers.ts index 552a6c4e..acd3e622 100644 --- a/src/firmware/reducers.ts +++ b/src/firmware/reducers.ts @@ -1,11 +1,10 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2021-2025 The Pybricks Authors +// Copyright (c) 2021-2026 The Pybricks Authors import { Reducer, combineReducers } from 'redux'; import { didFailToFinish, didFinish, - didProgress, didStart, firmwareDidFailToFlashUsbDfu, firmwareDidFailToRestoreOfficialDfu, @@ -33,18 +32,6 @@ const flashing: Reducer = (state = false, action) => { return state; }; -const progress: Reducer = (state = null, action) => { - if (didStart.matches(action)) { - return null; - } - - if (didProgress.matches(action)) { - return action.value; - } - - return state; -}; - const isFirmwareFlashUsbDfuInProgress: Reducer = (state = false, action) => { if (firmwareFlashUsbDfu.matches(action)) { return true; @@ -100,7 +87,6 @@ export default combineReducers({ installPybricksDialog, restoreOfficialDialog, flashing, - progress, isFirmwareFlashUsbDfuInProgress, isFirmwareRestoreOfficialDfuInProgress, isFirmwareFlashEV3InProgress, diff --git a/src/firmware/sagas.test.ts b/src/firmware/sagas.test.ts index 303c61c5..97d446a4 100644 --- a/src/firmware/sagas.test.ts +++ b/src/firmware/sagas.test.ts @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2021-2024 The Pybricks Authors +// Copyright (c) 2021-2026 The Pybricks Authors import { FirmwareMetadata, @@ -43,7 +43,6 @@ import { MetadataProblem, didFailToFinish, didFinish, - didProgress, didStart, flashFirmware as flashFirmwareAction, } from './actions'; @@ -175,9 +174,6 @@ describe('flashFirmware', () => { saga.put(didRequest(id)); - action = await saga.take(); - expect(action).toEqual(didProgress(offset / totalFirmwareSize)); - action = await saga.take(); expect(action).toEqual( alertsShowAlert( @@ -213,9 +209,6 @@ describe('flashFirmware', () => { saga.put(programResponse(0x33, totalFirmwareSize)); - action = await saga.take(); - expect(action).toEqual(didProgress(1)); - action = await saga.take(); expect(action).toEqual( alertsShowAlert( @@ -341,9 +334,6 @@ describe('flashFirmware', () => { saga.put(didRequest(id)); - action = await saga.take(); - expect(action).toEqual(didProgress(offset / totalFirmwareSize)); - action = await saga.take(); expect(action).toEqual( alertsShowAlert( @@ -379,9 +369,6 @@ describe('flashFirmware', () => { saga.put(programResponse(0xe0, totalFirmwareSize)); - action = await saga.take(); - expect(action).toEqual(didProgress(1)); - action = await saga.take(); expect(action).toEqual( alertsShowAlert( @@ -1306,9 +1293,6 @@ describe('flashFirmware', () => { saga.put(didRequest(id)); - action = await saga.take(); - expect(action).toEqual(didProgress(offset / totalFirmwareSize)); - action = await saga.take(); expect(action).toEqual( alertsShowAlert( @@ -1482,9 +1466,6 @@ describe('flashFirmware', () => { saga.put(didRequest(id)); - action = await saga.take(); - expect(action).toEqual(didProgress(offset / totalFirmwareSize)); - action = await saga.take(); expect(action).toEqual( alertsShowAlert( @@ -1664,9 +1645,6 @@ describe('flashFirmware', () => { saga.put(didRequest(id)); - action = await saga.take(); - expect(action).toEqual(didProgress(offset / totalFirmwareSize)); - action = await saga.take(); expect(action).toEqual( alertsShowAlert( @@ -1702,9 +1680,6 @@ describe('flashFirmware', () => { saga.put(programResponse(0xf3, totalFirmwareSize)); - action = await saga.take(); - expect(action).toEqual(didProgress(1)); - action = await saga.take(); expect(action).toEqual( alertsShowAlert( @@ -2230,9 +2205,6 @@ describe('flashFirmware', () => { saga.put(didRequest(id)); - action = await saga.take(); - expect(action).toEqual(didProgress(offset / totalFirmwareSize)); - action = await saga.take(); expect(action).toEqual( alertsShowAlert( @@ -2267,9 +2239,6 @@ describe('flashFirmware', () => { saga.put(programResponse(0x27, totalFirmwareSize)); - action = await saga.take(); - expect(action).toEqual(didProgress(1)); - action = await saga.take(); expect(action).toEqual( alertsShowAlert( diff --git a/src/firmware/sagas.ts b/src/firmware/sagas.ts index 7ac4fea7..be910740 100644 --- a/src/firmware/sagas.ts +++ b/src/firmware/sagas.ts @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -// Copyright (c) 2020-2025 The Pybricks Authors +// Copyright (c) 2020-2026 The Pybricks Authors import { FirmwareReader, @@ -75,7 +75,6 @@ import { MetadataProblem, didFailToFinish, didFinish, - didProgress, didStart, firmwareDidFailToFlashEV3, firmwareDidFailToFlashUsbDfu, @@ -531,8 +530,6 @@ function* handleFlashFirmware(action: ReturnType): Generat ); yield* waitForDidRequest(programAction.id); - yield* put(didProgress(offset / firmware.length)); - yield* put( alertsShowAlert( 'firmware', @@ -617,8 +614,6 @@ function* handleFlashFirmware(action: ReturnType): Generat yield* disconnectAndCancel(); } - yield* put(didProgress(1)); - yield* put( alertsShowAlert( 'firmware', @@ -754,6 +749,15 @@ function* handleFlashUsbDfu(action: ReturnType): Gen return; } + if ( + device.productId === LegoUsbProductId.SpikePrimeBootloader && + device.deviceVersionMajor !== 1 + ) { + yield* put(alertsShowAlert('firmware', 'unsupportedDfuHub')); + yield* put(firmwareDidFailToFlashUsbDfu()); + return; + } + const dfu = new WebDFU( device, // forceInterfacesName is needed to get the flash layout map @@ -1033,6 +1037,7 @@ function* handleRestoreOfficialDfu( } } +const firmwareEv3ProgressToastId = 'firmware.ev3.progress'; const getNextEV3MessageId = createCountFunc(); function* handleFlashEV3(action: ReturnType): Generator { @@ -1252,6 +1257,7 @@ function* handleFlashEV3(action: ReturnType): Generator error: eraseError, }), ); + yield* put(alertsHideAlert(firmwareEv3ProgressToastId)); // FIXME: should have a better error reason yield* put(didFailToFinish(FailToFinishReasonType.Unknown, eraseError)); yield* put(firmwareDidFailToFlashEV3()); @@ -1269,6 +1275,7 @@ function* handleFlashEV3(action: ReturnType): Generator error: sendError, }), ); + yield* put(alertsHideAlert(firmwareEv3ProgressToastId)); // FIXME: should have a better error reason yield* put(didFailToFinish(FailToFinishReasonType.Unknown, sendError)); yield* put(firmwareDidFailToFlashEV3()); @@ -1277,10 +1284,6 @@ function* handleFlashEV3(action: ReturnType): Generator } } - yield* put( - didProgress((i + sectorData.byteLength) / action.firmware.byteLength), - ); - yield* put( alertsShowAlert( 'firmware', @@ -1289,7 +1292,7 @@ function* handleFlashEV3(action: ReturnType): Generator action: 'flash', progress: (i + sectorData.byteLength) / action.firmware.byteLength, }, - firmwareBleProgressToastId, + firmwareEv3ProgressToastId, true, ), ); @@ -1303,7 +1306,7 @@ function* handleFlashEV3(action: ReturnType): Generator action: 'flash', progress: 1, }, - firmwareBleProgressToastId, + firmwareEv3ProgressToastId, true, ), );