firmware/installPybricksDialog: drop support for including custom main.py

This removes the UI and internal support for selecting a file to include
as the main.py when flashing firmware. This feature was removed in the
firmware starting with firmware metadata v2.0.0 so the firmware that
ships with Pybricks code won't be able to do this in the near future.
So, it doesn't make sense to keep this feature only for old firmware.

It is still possible to replace the main.py in old firmware.zip files
and flash that way.
This commit is contained in:
David Lechner
2022-09-14 18:12:34 -05:00
parent 9811807d09
commit 56630ef2cf
6 changed files with 23 additions and 218 deletions
+1 -4
View File
@@ -120,15 +120,12 @@ export type FailToFinishReason =
/**
* Creates a new action to flash firmware to a hub.
* @param data The firmware zip file data or `null` to get firmware later.
* @param customProgram If defined, flash the path of a program from file storage,
* otherwise use the main.py program from firmware.zip.
* @param hubName A custom hub name or an empty string to use the default name.
*/
export const flashFirmware = createAction(
(data: ArrayBuffer | null, customProgram: string | undefined, hubName: string) => ({
(data: ArrayBuffer | null, hubName: string) => ({
type: 'flashFirmware.action.flashFirmware',
data,
customProgram,
hubName,
}),
);
@@ -7,7 +7,6 @@ import {
Callout,
Checkbox,
Classes,
Code,
Collapse,
ControlGroup,
DialogStep,
@@ -15,15 +14,12 @@ import {
Icon,
InputGroup,
Intent,
MenuItem,
MultistepDialog,
NonIdealState,
Pre,
Spinner,
Switch,
} from '@blueprintjs/core';
import { Classes as Classes2, Popover2 } from '@blueprintjs/popover2';
import { Select2 } from '@blueprintjs/select';
import { FirmwareMetadata, HubType } from '@pybricks/firmware';
import { fileOpen } from 'browser-fs-access';
import classNames from 'classnames';
@@ -33,7 +29,6 @@ import { useDispatch } from 'react-redux';
import { useLocalStorage } from 'usehooks-ts';
import { alertsShowAlert } from '../../alerts/actions';
import {
appName,
pybricksUsbDfuWindowsDriverInstallUrl,
pybricksUsbLinuxUdevRulesUrl,
} from '../../app/constants';
@@ -42,13 +37,10 @@ import {
Hub,
hubBootloaderType,
hubHasBluetoothButton,
hubHasExternalFlash,
hubHasUSB,
} from '../../components/hubPicker';
import { HubPicker } from '../../components/hubPicker/HubPicker';
import { useHubPickerSelectedHub } from '../../components/hubPicker/hooks';
import { FileMetadata } from '../../fileStorage';
import { useFileStorageMetadata } from '../../fileStorage/hooks';
import { useSelector } from '../../reducers';
import { ensureError } from '../../utils';
import ExternalLinkIcon from '../../utils/ExternalLinkIcon';
@@ -363,27 +355,16 @@ const AcceptLicensePanel: React.VoidFunctionComponent<AcceptLicensePanelProps> =
};
type SelectOptionsPanelProps = {
hubType: Hub;
hubName: string;
includeProgram: boolean;
selectedIncludeFile: FileMetadata | undefined;
onChangeHubName(hubName: string): void;
onChangeIncludeProgram(includeProgram: boolean): void;
onChangeSelectedIncludeFile(selectedIncludeFile: FileMetadata | undefined): void;
};
const ConfigureOptionsPanel: React.VoidFunctionComponent<SelectOptionsPanelProps> = ({
hubType,
hubName,
includeProgram,
selectedIncludeFile,
onChangeHubName,
onChangeIncludeProgram,
onChangeSelectedIncludeFile,
}) => {
const i18n = useI18n();
const isHubNameValid = validateHubName(hubName);
const files = useFileStorageMetadata();
return (
<div className={dialogBody}>
@@ -415,86 +396,6 @@ const ConfigureOptionsPanel: React.VoidFunctionComponent<SelectOptionsPanelProps
/>
</ControlGroup>
</FormGroup>
<FormGroup
label={i18n.translate('optionsPanel.customMain.label')}
labelInfo={i18n.translate('optionsPanel.customMain.labelInfo')}
>
{(hubHasExternalFlash(hubType) && (
<p>
{i18n.translate(
'optionsPanel.customMain.notApplicable.message',
)}
</p>
)) || (
<ControlGroup>
<Switch
labelElement={i18n.translate(
'optionsPanel.customMain.include.label',
{ main: <Code>main.py</Code> },
)}
checked={includeProgram}
onChange={(e) =>
onChangeIncludeProgram(
(e.target as HTMLInputElement).checked,
)
}
/>
<Select2
items={files || []}
itemRenderer={(
item,
{ handleClick, handleFocus, modifiers },
) => (
<MenuItem
roleStructure="listoption"
active={modifiers.active}
disabled={modifiers.disabled}
text={item.path}
key={item.uuid}
onClick={handleClick}
onFocus={handleFocus}
/>
)}
noResults={
<MenuItem
roleStructure="listoption"
disabled={true}
text={i18n.translate(
'optionsPanel.customMain.include.noFiles',
)}
/>
}
filterable={false}
popoverProps={{ minimal: true }}
disabled={!includeProgram}
onItemSelect={onChangeSelectedIncludeFile}
>
<Button
icon="double-caret-vertical"
text={
selectedIncludeFile?.path ??
i18n.translate(
'optionsPanel.customMain.include.noSelection',
)
}
disabled={!includeProgram}
/>
</Select2>
<HelpButton
helpForLabel={i18n.translate(
'optionsPanel.customMain.include.label',
{ main: 'main.py' },
)}
content={i18n.translate(
'optionsPanel.customMain.include.help',
{
appName,
},
)}
/>
</ControlGroup>
)}
</FormGroup>
</div>
);
};
@@ -617,8 +518,6 @@ export const InstallPybricksDialog: React.VoidFunctionComponent = () => {
const { isOpen } = useSelector((s) => s.firmware.installPybricksDialog);
const dispatch = useDispatch();
const [hubName, setHubName] = useState('');
const [includeProgram, setIncludeProgram] = useState(false);
const [selectedIncludeFile, setSelectedIncludeFile] = useState<FileMetadata>();
const [licenseAccepted, setLicenseAccepted] = useState(false);
const [hubType] = useHubPickerSelectedHub();
const { firmwareData } = useFirmware(hubType);
@@ -646,7 +545,6 @@ export const InstallPybricksDialog: React.VoidFunctionComponent = () => {
firmwareInstallPybricksDialogAccept(
hubBootloaderType(selectedHubType),
selectedFirmwareData?.firmwareZip ?? new ArrayBuffer(0),
selectedIncludeFile?.path,
hubName,
),
),
@@ -685,13 +583,8 @@ export const InstallPybricksDialog: React.VoidFunctionComponent = () => {
title={i18n.translate('optionsPanel.title')}
panel={
<ConfigureOptionsPanel
hubType={selectedHubType}
hubName={hubName}
includeProgram={includeProgram}
selectedIncludeFile={selectedIncludeFile}
onChangeHubName={setHubName}
onChangeIncludeProgram={setIncludeProgram}
onChangeSelectedIncludeFile={setSelectedIncludeFile}
/>
}
backButtonProps={{ text: i18n.translate('backButton.label') }}
@@ -14,20 +14,13 @@ type FlashMethod = 'ble-lwp3-bootloader' | 'usb-lego-dfu';
* Action that indicates the user accepted the install Pybricks firmware dialog.
* @param flashMethod The connection method and protocol used for flashing.
* @param firmwareZip The firmware.zip raw data.
* @param customProgram Optional path of custom program to include when flashing firmware.
* @param hubName The hub name to use when flashing firmware.
*/
export const firmwareInstallPybricksDialogAccept = createAction(
(
flashMethod: FlashMethod,
firmwareZip: ArrayBuffer,
customProgram: string | undefined,
hubName: string,
) => ({
(flashMethod: FlashMethod, firmwareZip: ArrayBuffer, hubName: string) => ({
type: 'firmware.installPybricksDialog.action.accept',
flashMethod,
firmwareZip,
customProgram,
hubName,
}),
);
@@ -50,19 +50,6 @@
"labelInfo": "(optional)",
"help": "Enter a name here to customize the hub name when flashing the firmware. This name will be used in the Bluetooth advertising data and can be used to identify the hub when connecting.",
"error": "The name is too long."
},
"customMain": {
"label": "Include custom program",
"labelInfo": "(optional)",
"notApplicable": {
"message": "This hub has external flash memory so including a custom program when flashing firmware is not needed."
},
"include": {
"label": "Include selected program as {main}",
"noSelection": "(no selection)",
"noFiles": "(no files)",
"help": "Enable to include your program when flashing the firmware or disable to use the default program. Flashing your program along with the firmware will allow you to run your program without being connected to {appName}"
}
}
},
"bootloaderPanel": {
+15 -22
View File
@@ -88,7 +88,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, 'test name'));
saga.put(flashFirmwareAction(null, 'test name'));
// first step is to connect to the hub bootloader
@@ -236,7 +236,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, 'test name'));
saga.put(flashFirmwareAction(null, 'test name'));
// first step is to connect to the hub bootloader
@@ -366,7 +366,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -416,7 +416,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -484,7 +484,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -548,7 +548,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -615,7 +615,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -675,7 +675,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -741,7 +741,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -824,7 +824,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -890,7 +890,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -989,7 +989,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -1097,7 +1097,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -1245,7 +1245,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
@@ -1394,7 +1394,6 @@ describe('flashFirmware', () => {
saga.put(
flashFirmwareAction(
await zip.generateAsync({ type: 'arraybuffer' }),
undefined,
'',
),
);
@@ -1547,7 +1546,6 @@ describe('flashFirmware', () => {
saga.put(
flashFirmwareAction(
await zip.generateAsync({ type: 'arraybuffer' }),
undefined,
'',
),
);
@@ -1595,7 +1593,6 @@ describe('flashFirmware', () => {
saga.put(
flashFirmwareAction(
await zip.generateAsync({ type: 'arraybuffer' }),
undefined,
'',
),
);
@@ -1642,7 +1639,6 @@ describe('flashFirmware', () => {
saga.put(
flashFirmwareAction(
await zip.generateAsync({ type: 'arraybuffer' }),
undefined,
'',
),
);
@@ -1703,7 +1699,6 @@ describe('flashFirmware', () => {
saga.put(
flashFirmwareAction(
await zip.generateAsync({ type: 'arraybuffer' }),
undefined,
'',
),
);
@@ -1765,7 +1760,6 @@ describe('flashFirmware', () => {
saga.put(
flashFirmwareAction(
await zip.generateAsync({ type: 'arraybuffer' }),
undefined,
'',
),
);
@@ -1830,7 +1824,6 @@ describe('flashFirmware', () => {
saga.put(
flashFirmwareAction(
await zip.generateAsync({ type: 'arraybuffer' }),
undefined,
'',
),
);
@@ -1917,7 +1910,7 @@ describe('flashFirmware', () => {
// saga is triggered by this action
saga.put(flashFirmwareAction(null, undefined, ''));
saga.put(flashFirmwareAction(null, ''));
// first step is to connect to the hub bootloader
+6 -64
View File
@@ -31,11 +31,6 @@ import {
takeEvery,
} from 'typed-redux-saga/macro';
import { alertsDidShowAlert, alertsShowAlert } from '../alerts/actions';
import {
fileStorageDidFailToReadFile,
fileStorageDidReadFile,
fileStorageReadFile,
} from '../fileStorage/actions';
import {
checksumRequest,
checksumResponse,
@@ -192,11 +187,10 @@ function* firmwareIterator(data: DataView, maxSize: number): Generator<number> {
* Loads Pybricks firmware from a .zip file.
*
* @param data The zip file raw data
* @param program User program or `undefined` to use main.py from firmware.zip
* @param hubName Optional custom name for the hub.
*/
function* loadFirmware(
data: ArrayBuffer,
program: string | undefined,
hubName: string,
): SagaGenerator<{ firmware: Uint8Array; deviceId: HubType }> {
const [reader, readerErr] = yield* call(() => maybe(FirmwareReader.load(data)));
@@ -223,16 +217,7 @@ function* loadFirmware(
// v1.x allows appending main.py to firmware, later versions do not
if (metadataIsV100(metadata) || metadataIsV110(metadata)) {
// if a user program was not given, then use main.py from the firmware.zip
if (program === undefined) {
program = yield* call(() => reader.readMainPy());
}
// REVISIT: the firmware may eventually be changed to allow no main.py
// for now, ensure there is a program even if it does nothing
if (!program) {
program = '';
}
const program = (yield* call(() => reader.readMainPy())) ?? '';
if (![5, 6].includes(metadata['mpy-abi-version'])) {
yield* put(
@@ -396,37 +381,8 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
let firmware: Uint8Array | undefined = undefined;
let deviceId: HubType | undefined = undefined;
let program: string | undefined = undefined;
if (action.customProgram) {
yield* put(fileStorageReadFile(action.customProgram));
const { didRead, didFailToRead } = yield* race({
didRead: take(
fileStorageDidReadFile.when((a) => a.path === action.customProgram),
),
didFailToRead: take(
fileStorageDidFailToReadFile.when(
(a) => a.path === action.customProgram,
),
),
});
if (didFailToRead) {
throw didFailToRead.error;
}
defined(didRead);
program = didRead.contents;
}
if (action.data !== null) {
({ firmware, deviceId } = yield* loadFirmware(
action.data,
program,
action.hubName,
));
({ firmware, deviceId } = yield* loadFirmware(action.data, action.hubName));
}
yield* put(connect());
@@ -468,11 +424,7 @@ function* handleFlashFirmware(action: ReturnType<typeof flashFirmware>): Generat
}
const data = yield* call(() => response.arrayBuffer());
({ firmware, deviceId } = yield* loadFirmware(
data,
program,
action.hubName,
));
({ firmware, deviceId } = yield* loadFirmware(data, action.hubName));
if (deviceId !== undefined && info.hubType !== deviceId) {
yield* put(didFailToFinish(FailToFinishReasonType.DeviceMismatch));
@@ -746,11 +698,7 @@ function* handleFlashUsbDfu(action: ReturnType<typeof firmwareFlashUsbDfu>): Gen
}),
);
const { firmware, deviceId } = yield* loadFirmware(
action.data,
undefined,
action.hubName,
);
const { firmware, deviceId } = yield* loadFirmware(action.data, action.hubName);
if (deviceId !== productIdMap.get(device.productId)) {
yield* put(alertsShowAlert('firmware', 'firmwareMismatch'));
@@ -876,13 +824,7 @@ function* handleInstallPybricks(): Generator {
switch (accepted.flashMethod) {
case 'ble-lwp3-bootloader':
yield* put(
flashFirmware(
accepted.firmwareZip,
accepted.customProgram,
accepted.hubName,
),
);
yield* put(flashFirmware(accepted.firmwareZip, accepted.hubName));
break;
case 'usb-lego-dfu':
yield* put(firmwareFlashUsbDfu(accepted.firmwareZip, accepted.hubName));