mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 17:45:22 +00:00
firmware/sagas: show progress for BLE flashing
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2022 The Pybricks Authors
|
||||
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { CreateToast } from '../../i18nToaster';
|
||||
import { useI18n } from './i18n';
|
||||
|
||||
const ReleaseButton: React.VoidFunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
return <p>{i18n.translate('releaseButton.message')}</p>;
|
||||
};
|
||||
|
||||
export const releaseButton: CreateToast = (onAction) => {
|
||||
return {
|
||||
message: <ReleaseButton />,
|
||||
icon: 'info-sign',
|
||||
intent: Intent.PRIMARY,
|
||||
onDismiss: () => onAction('dismiss'),
|
||||
};
|
||||
};
|
||||
@@ -5,10 +5,12 @@ import { firmwareMismatch } from './FirmwareMismatch';
|
||||
import { noDfuHub } from './NoDfuHub';
|
||||
import { noDfuInterface } from './NoDfuInterface';
|
||||
import { noWebUsb } from './NoWebUsb';
|
||||
import { releaseButton } from './ReleaseButton';
|
||||
|
||||
export default {
|
||||
firmwareMismatch,
|
||||
noDfuHub,
|
||||
noDfuInterface,
|
||||
noWebUsb,
|
||||
releaseButton,
|
||||
};
|
||||
|
||||
@@ -21,5 +21,8 @@
|
||||
"flashProgress": {
|
||||
"erasing": "Erasing internal flash memory: {percent}",
|
||||
"flashing": "Writing new firmware: {percent}"
|
||||
},
|
||||
"releaseButton": {
|
||||
"message": "You may release the power button on the hub now."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021-2022 The Pybricks Authors
|
||||
|
||||
import { IToaster } from '@blueprintjs/core';
|
||||
import {
|
||||
FirmwareMetadata,
|
||||
FirmwareReaderError,
|
||||
FirmwareReaderErrorCode,
|
||||
} from '@pybricks/firmware';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import JSZip from 'jszip';
|
||||
import { AsyncSaga } from '../../test';
|
||||
import { alertsShowAlert } from '../alerts/actions';
|
||||
import {
|
||||
BootloaderConnectionFailureReason,
|
||||
checksumRequest,
|
||||
@@ -78,6 +81,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -130,6 +134,9 @@ describe('flashFirmware', () => {
|
||||
|
||||
// erase first
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(alertsShowAlert('firmware', 'releaseButton'));
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(eraseRequest(1, /* isCityHub */ false));
|
||||
|
||||
@@ -224,6 +231,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -273,6 +281,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -340,6 +349,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -403,6 +413,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -469,6 +480,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -528,6 +540,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -593,6 +606,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -675,6 +689,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -740,6 +755,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -790,6 +806,9 @@ describe('flashFirmware', () => {
|
||||
|
||||
// erase first
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(alertsShowAlert('firmware', 'releaseButton'));
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(eraseRequest(1, /* isCityHub */ false));
|
||||
|
||||
@@ -835,6 +854,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -885,6 +905,9 @@ describe('flashFirmware', () => {
|
||||
|
||||
// erase first
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(alertsShowAlert('firmware', 'releaseButton'));
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(eraseRequest(1, /* isCityHub */ false));
|
||||
|
||||
@@ -939,6 +962,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -989,6 +1013,9 @@ describe('flashFirmware', () => {
|
||||
|
||||
// erase first
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(alertsShowAlert('firmware', 'releaseButton'));
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(eraseRequest(1, /* isCityHub */ false));
|
||||
|
||||
@@ -1083,6 +1110,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -1133,6 +1161,9 @@ describe('flashFirmware', () => {
|
||||
|
||||
// erase first
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(alertsShowAlert('firmware', 'releaseButton'));
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(eraseRequest(1, /* isCityHub */ false));
|
||||
|
||||
@@ -1225,6 +1256,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -1283,6 +1315,9 @@ describe('flashFirmware', () => {
|
||||
|
||||
// erase first
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(alertsShowAlert('firmware', 'releaseButton'));
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(eraseRequest(1, /* isCityHub */ false));
|
||||
|
||||
@@ -1374,6 +1409,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -1421,6 +1457,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -1467,6 +1504,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -1527,6 +1565,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -1588,6 +1627,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -1652,6 +1692,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -1741,6 +1782,7 @@ describe('flashFirmware', () => {
|
||||
|
||||
const saga = new AsyncSaga(flashFirmware, {
|
||||
nextMessageId: createCountFunc(),
|
||||
toaster: mock<IToaster>(),
|
||||
});
|
||||
|
||||
// saga is triggered by this action
|
||||
@@ -1791,12 +1833,14 @@ describe('flashFirmware', () => {
|
||||
|
||||
// erase first
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(alertsShowAlert('firmware', 'releaseButton'));
|
||||
|
||||
action = await saga.take();
|
||||
expect(action).toEqual(eraseRequest(1, /* isCityHub */ false));
|
||||
|
||||
saga.put(didRequest(1));
|
||||
saga.put(eraseResponse(Result.OK));
|
||||
|
||||
// then write the new firmware
|
||||
|
||||
const totalFirmwareSize = metadata['user-mpy-offset'] + mpySize + 8;
|
||||
|
||||
@@ -94,6 +94,10 @@ const firmwareZipMap = new Map<HubType, string>([
|
||||
* parent task).
|
||||
*/
|
||||
function* disconnectAndCancel(): SagaGenerator<void> {
|
||||
const toaster = yield* getContext<IToaster>('toaster');
|
||||
|
||||
toaster.dismiss('firmware.ble.progress');
|
||||
|
||||
const connection = yield* select((s: RootState) => s.bootloader.connection);
|
||||
|
||||
if (connection === BootloaderConnectionState.Connected) {
|
||||
@@ -328,6 +332,8 @@ function* loadFirmware(
|
||||
* @param action The action that triggered this saga.
|
||||
*/
|
||||
function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generator {
|
||||
const toaster = yield* getContext<IToaster>('toaster');
|
||||
|
||||
try {
|
||||
let firmware: Uint8Array | undefined = undefined;
|
||||
let deviceId: HubType | undefined = undefined;
|
||||
@@ -418,6 +424,16 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
|
||||
|
||||
yield* put(didStart());
|
||||
|
||||
toaster.show(
|
||||
flashProgress(() => undefined, {
|
||||
action: 'erase',
|
||||
progress: undefined,
|
||||
}),
|
||||
'firmware.ble.progress',
|
||||
);
|
||||
|
||||
yield* put(alertsShowAlert('firmware', 'releaseButton'));
|
||||
|
||||
const eraseAction = yield* put(
|
||||
eraseRequest(nextMessageId(), deviceId === HubType.CityHub),
|
||||
);
|
||||
@@ -469,6 +485,14 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
|
||||
|
||||
yield* put(didProgress(offset / firmware.length));
|
||||
|
||||
toaster.show(
|
||||
flashProgress(() => undefined, {
|
||||
action: 'flash',
|
||||
progress: offset / firmware.length,
|
||||
}),
|
||||
'firmware.ble.progress',
|
||||
);
|
||||
|
||||
// we don't want to request checksum if this is the last packet since
|
||||
// the bootloader will send a response to the program request already.
|
||||
offset += maxDataSize;
|
||||
@@ -542,6 +566,14 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
|
||||
|
||||
yield* put(didProgress(1));
|
||||
|
||||
toaster.show(
|
||||
flashProgress(() => undefined, {
|
||||
action: 'flash',
|
||||
progress: 1,
|
||||
}),
|
||||
'firmware.ble.progress',
|
||||
);
|
||||
|
||||
// this will cause the remote device to disconnect and reboot
|
||||
const rebootAction = yield* put(rebootRequest(nextMessageId()));
|
||||
yield* waitForDidRequest(rebootAction.id);
|
||||
|
||||
Reference in New Issue
Block a user