add disconnect monitor to firmware flash sagas

This commit is contained in:
David Lechner
2021-01-22 15:58:10 -06:00
parent 2d44705112
commit 18edc7870e
3 changed files with 115 additions and 0 deletions
+5
View File
@@ -50,6 +50,8 @@ type Reason<T> = {
export enum FailToStartReasonType {
/** Connecting to the hub failed. */
FailedToConnect = 'flashFirmware.failToStart.reason.failedToConnect',
/** The hub was disconnected. */
Disconnected = 'flashFirmware.failToStart.reason.disconnected',
/** The is no firmware available that matches the connected hub. */
NoFirmware = 'flashFirmware.failToStart.reason.noFirmware',
/** The provided firmware.zip does not match the connected hub. */
@@ -68,6 +70,8 @@ export enum FailToStartReasonType {
export type FailToStartReasonFailedToConnect = Reason<FailToStartReasonType.FailedToConnect>;
export type FailToStartReasonDisconnected = Reason<FailToStartReasonType.Disconnected>;
export type FailToStartReasonNoFirmware = Reason<FailToStartReasonType.NoFirmware>;
export type FailToStartReasonDeviceMismatch = Reason<FailToStartReasonType.DeviceMismatch>;
@@ -91,6 +95,7 @@ export type FailToStartReasonUnknown = Reason<FailToStartReasonType.Unknown> & {
export type FailToStartReason =
| FailToStartReasonFailedToConnect
| FailToStartReasonDisconnected
| FailToStartReasonNoFirmware
| FailToStartReasonDeviceMismatch
| FailToStartReasonZipError
+59
View File
@@ -24,6 +24,7 @@ import {
checksumResponse,
connect,
didConnect,
didDisconnect,
didFailToConnect,
didRequest,
eraseRequest,
@@ -238,6 +239,64 @@ describe('flashFirmware', () => {
await saga.end();
});
test('untimely disconnect before start cancels saga', async () => {
const metadata: FirmwareMetadata = {
'metadata-version': '1.0.0',
'device-id': HubType.MoveHub,
'checksum-type': 'sum',
'firmware-version': '1.2.3',
'max-firmware-size': 1024,
'mpy-abi-version': 5,
'mpy-cross-options': ['-mno-unicode'],
'user-mpy-offset': 100,
};
const zip = new JSZip();
zip.file('firmware-base.bin', new Uint8Array(64));
zip.file('firmware.metadata.json', JSON.stringify(metadata));
zip.file('main.py', 'print("test")');
zip.file('ReadMe_OSS.txt', 'test');
jest.spyOn(window, 'fetch').mockResolvedValueOnce(
new Response(await zip.generateAsync({ type: 'blob' })),
);
const saga = new AsyncSaga(flashFirmware, {
nextMessageId: createCountFunc(),
});
saga.setState({ settings: { flashCurrentProgram: false } });
// saga is triggered by this action
saga.put(flashFirmwareAction());
// first step is to connect to the hub bootloader
let action = await saga.take();
expect(action).toEqual(connect());
saga.put(didConnect());
// then find out what kind of hub it is
action = await saga.take();
expect(action).toEqual(infoRequest(0));
saga.put(didRequest(0));
// hub disconnects before replying
saga.put(didDisconnect());
// should get a failure to start
action = await saga.take();
expect(action).toEqual(didFailToStart(FailToStartReasonType.Disconnected));
await saga.end();
});
});
describe('user supplied firmware.zip', () => {
+51
View File
@@ -11,6 +11,7 @@ import {
call,
cancel,
delay,
fork,
getContext,
put,
race,
@@ -19,11 +20,14 @@ import {
takeEvery,
} from 'typed-redux-saga/macro';
import { Action } from '../actions';
import { disconnect } from '../actions/ble';
import {
FailToFinishReasonType,
FailToStartReasonType,
FlashFirmwareActionType,
FlashFirmwareFlashAction,
MetadataProblem,
didFailToFinish,
didFailToStart,
didFinish,
didProgress,
@@ -201,6 +205,50 @@ function* loadFirmware(
return { firmware, deviceId: metadata['device-id'] };
}
/**
* The purpose of this function is two-fold. If the BLE device is disconnected,
* then it will raise a failure action and cancel the task (including the
* parent task). Or, if the parent task fails, it will disconnect the BLE device
* and return.
*/
function* disconnectMonitor(): SagaGenerator<void> {
const { disconnectedBeforeStart, failedToStart } = yield* race({
disconnectedBeforeStart: take(BootloaderConnectionActionType.DidDisconnect),
started: take(FlashFirmwareActionType.DidStart),
failedToStart: take(FlashFirmwareActionType.DidFailToStart),
});
if (disconnectedBeforeStart) {
yield* put(didFailToStart(FailToStartReasonType.Disconnected));
yield* cancel();
}
if (failedToStart) {
yield* put(disconnect());
return;
}
// if we get here, `started` won the race
const { disconnectedAfterStart, failedToFinish } = yield* race({
disconnectedAfterStart: take(BootloaderConnectionActionType.DidDisconnect),
finished: take(FlashFirmwareActionType.DidFinish),
failedToFinish: take(FlashFirmwareActionType.DidFailToFinish),
});
if (disconnectedAfterStart) {
yield* put(didFailToFinish(FailToFinishReasonType.Disconnected));
yield* cancel();
}
if (failedToFinish) {
yield* put(disconnect());
return;
}
// if we get here, `finished` won the race.
}
/**
* Flashes firmware to a Powered Up device.
* @param action The action that triggered this saga.
@@ -242,6 +290,8 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
return;
}
const disconnectMonitorTask = yield* fork(disconnectMonitor);
const nextMessageId = yield* getContext<() => number>('nextMessageId');
const infoAction = yield* put(infoRequest(nextMessageId()));
@@ -378,6 +428,7 @@ function* flashFirmware(action: FlashFirmwareFlashAction): Generator {
// this will cause the remote device to disconnect and reboot
const rebootAction = yield* put(rebootRequest(nextMessageId()));
disconnectMonitorTask.cancel();
yield* waitForDidRequest(rebootAction.id);
yield* put(didFinish());