From 1148b169d5733063bfee19d6e7e38093175da7f6 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Tue, 26 Jul 2022 15:18:44 -0500 Subject: [PATCH] firmware: add progress indication for DFU USB flash --- src/firmware/alerts/FlashProgress.tsx | 54 ++++++++++++++++++++++++ src/firmware/alerts/translations/en.json | 4 ++ src/firmware/sagas.ts | 30 +++++++++++-- 3 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/firmware/alerts/FlashProgress.tsx diff --git a/src/firmware/alerts/FlashProgress.tsx b/src/firmware/alerts/FlashProgress.tsx new file mode 100644 index 00000000..3594488e --- /dev/null +++ b/src/firmware/alerts/FlashProgress.tsx @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2022 The Pybricks Authors + +import { Intent, ProgressBar } from '@blueprintjs/core'; +import React from 'react'; +import { CreateToast } from '../../i18nToaster'; +import { useI18n } from './i18n'; + +type FlashProgressProps = { + action: 'erase' | 'flash'; + progress: number | undefined; +}; + +const FlashProgress: React.VoidFunctionComponent = ({ + action, + progress, +}) => { + const i18n = useI18n(); + + return ( + <> + {action === 'erase' && ( +

+ {i18n.translate('flashProgress.erasing', { + percent: progress ? i18n.formatPercentage(progress) : '', + })} +

+ )} + + {action === 'flash' && ( +

+ {i18n.translate('flashProgress.flashing', { + percent: progress ? i18n.formatPercentage(progress) : '', + })} +

+ )} + +

+ +

+ + ); +}; + +export const flashProgress: CreateToast = (onAction, props) => { + return { + message: , + icon: 'download', + intent: Intent.PRIMARY, + // close one second after progress is complete + timeout: (props.progress ?? 0) < 1 ? 0 : 1000, + onDismiss: () => onAction('dismiss'), + }; +}; diff --git a/src/firmware/alerts/translations/en.json b/src/firmware/alerts/translations/en.json index df5a2aa4..7a8f2f77 100644 --- a/src/firmware/alerts/translations/en.json +++ b/src/firmware/alerts/translations/en.json @@ -17,5 +17,9 @@ }, "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 42cfd915..6f7ca1ae 100644 --- a/src/firmware/sagas.ts +++ b/src/firmware/sagas.ts @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT // Copyright (c) 2020-2022 The Pybricks Authors +import { IToaster } from '@blueprintjs/core'; import { FirmwareReader, FirmwareReaderError, @@ -75,6 +76,7 @@ import { firmwareInstallPybricks, flashFirmware, } from './actions'; +import { flashProgress } from './alerts/FlashProgress'; import { firmwareInstallPybricksDialogAccept, firmwareInstallPybricksDialogCancel, @@ -615,12 +617,10 @@ function* handleFlashUsbDfu(action: ReturnType): Gen // forceInterfacesName is needed to get the flash layout map { forceInterfacesName: true }, { + // NB: info and progress are never called in dfu v0.1.5 info: console.debug, warning: console.warn, - progress: (progress, total) => { - // TODO: bind to eventChannel and dispatch progress actions - console.log(progress, total); - }, + progress: console.debug, }, ); @@ -656,6 +656,28 @@ function* handleFlashUsbDfu(action: ReturnType): Gen dfu.dfuseStartAddress = dfuFirmwareStartAddress; const writeProc = dfu.write(1024, firmware, true); + const toaster = yield* getContext('toaster'); + + writeProc.events.on('erase/process', (sent, total) => { + toaster.show( + flashProgress(() => undefined, { + action: 'erase', + progress: sent / total, + }), + 'firmware.dfu.progress', + ); + }); + + writeProc.events.on('write/process', (sent, total) => { + toaster.show( + flashProgress(() => undefined, { + action: 'flash', + progress: sent / total, + }), + 'firmware.dfu.progress', + ); + }); + writeProc.events.on('error', console.error); // REVISIT: we could possibly race the 'write/end' and 'error' events