firmware: add UI for EV3 firmware flashing

This is just the UI bits (new file template, Pybricks firmware install,
official firmware restore).

The Pybricks firmware flashing hangs because we don't have a EV3 firmware
yet in @pybricks/firmware. Official firmware restore has an alert()
placeholder so at least it does something.
This commit is contained in:
David Lechner
2025-08-09 21:51:47 -05:00
parent 8b5cab4760
commit 007640c7b6
14 changed files with 284 additions and 74 deletions
+1 -1
View File
@@ -13,7 +13,7 @@
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@pybricks/firmware": "7.22.0", "@pybricks/firmware": "7.22.0",
"@pybricks/ide-docs": "2.20.0", "@pybricks/ide-docs": "2.20.0",
"@pybricks/images": "^1.3.0", "@pybricks/images": "^1.4.0",
"@pybricks/jedi": "1.17.0", "@pybricks/jedi": "1.17.0",
"@pybricks/mpy-cross-v5": "^2.0.0", "@pybricks/mpy-cross-v5": "^2.0.0",
"@pybricks/mpy-cross-v6": "^2.0.0", "@pybricks/mpy-cross-v6": "^2.0.0",
+7 -1
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors // Copyright (c) 2022-2025 The Pybricks Authors
import './hubPicker.scss'; import './hubPicker.scss';
import { Radio, RadioGroup } from '@blueprintjs/core'; import { Radio, RadioGroup } from '@blueprintjs/core';
@@ -66,6 +66,12 @@ export const HubPicker: React.FunctionComponent<HubPickerProps> = ({ disabled })
label="MINDSTORMS Robot Inventor Hub" label="MINDSTORMS Robot Inventor Hub"
/> />
</Radio> </Radio>
<Radio value={Hub.EV3}>
<HubIcon
url={new URL('@pybricks/images/hub-ev3.png', import.meta.url)}
label="MINDSTORMS EV3"
/>
</Radio>
</RadioGroup> </RadioGroup>
); );
}; };
+7 -1
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors // Copyright (c) 2022-2025 The Pybricks Authors
/** Supported hub types. */ /** Supported hub types. */
export enum Hub { export enum Hub {
@@ -15,6 +15,8 @@ export enum Hub {
Prime = 'primehub', Prime = 'primehub',
/** SPIKE Essential hub */ /** SPIKE Essential hub */
Essential = 'essentialhub', Essential = 'essentialhub',
/** MINDSTORMS EV3 hub */
EV3 = 'ev3',
} }
/** /**
@@ -25,6 +27,7 @@ export function hubHasUSB(hub: Hub): boolean {
case Hub.Prime: case Hub.Prime:
case Hub.Essential: case Hub.Essential:
case Hub.Inventor: case Hub.Inventor:
case Hub.EV3:
return true; return true;
default: default:
return false; return false;
@@ -52,6 +55,7 @@ export function hubHasExternalFlash(hub: Hub): boolean {
case Hub.Prime: case Hub.Prime:
case Hub.Essential: case Hub.Essential:
case Hub.Inventor: case Hub.Inventor:
case Hub.EV3:
return true; return true;
default: default:
return false; return false;
@@ -69,5 +73,7 @@ export function hubBootloaderType(hub: Hub) {
case Hub.City: case Hub.City:
case Hub.Technic: case Hub.Technic:
return 'ble-lwp3-bootloader'; return 'ble-lwp3-bootloader';
case Hub.EV3:
return 'usb-ev3';
} }
} }
+17 -2
View File
@@ -309,7 +309,9 @@ export const language = <monaco.languages.IMonarchLanguage>{
*/ */
function createTemplate(hubClassName: string, deviceClassNames: string[]): string { function createTemplate(hubClassName: string, deviceClassNames: string[]): string {
return `from pybricks.hubs import ${hubClassName} return `from pybricks.hubs import ${hubClassName}
from pybricks.pupdevices import ${deviceClassNames.join(', ')} from pybricks.${
hubClassName === 'EV3Brick' ? 'ev3devices' : 'pupdevices'
} import ${deviceClassNames.join(', ')}
from pybricks.parameters import Button, Color, Direction, Port, Side, Stop from pybricks.parameters import Button, Color, Direction, Port, Side, Stop
from pybricks.robotics import DriveBase from pybricks.robotics import DriveBase
from pybricks.tools import wait, StopWatch from pybricks.tools import wait, StopWatch
@@ -325,7 +327,8 @@ type HubLabel =
| 'technichub' | 'technichub'
| 'inventorhub' | 'inventorhub'
| 'primehub' | 'primehub'
| 'essentialhub'; | 'essentialhub'
| 'ev3';
const templateSnippets: Array< const templateSnippets: Array<
Required< Required<
@@ -375,6 +378,18 @@ const templateSnippets: Array<
'ColorLightMatrix', 'ColorLightMatrix',
]), ]),
}, },
{
label: 'ev3',
documentation: 'Template for MINDSTORMS EV3 program.',
insertText: createTemplate('EV3Brick', [
'Motor',
'ColorSensor',
'GyroSensor',
'InfraredSensor',
'TouchSensor',
'UltrasonicSensor',
]),
},
]; ];
/** /**
+37 -1
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Copyright (c) 2020-2022 The Pybricks Authors // Copyright (c) 2020-2025 The Pybricks Authors
import { FirmwareReaderError, HubType } from '@pybricks/firmware'; import { FirmwareReaderError, HubType } from '@pybricks/firmware';
import { createAction } from '../actions'; import { createAction } from '../actions';
@@ -429,3 +429,39 @@ export const firmwareDidRestoreOfficialDfu = createAction(() => ({
export const firmwareDidFailToRestoreOfficialDfu = createAction(() => ({ export const firmwareDidFailToRestoreOfficialDfu = createAction(() => ({
type: 'firmware.action.didFailToRestoreOfficialDfu', type: 'firmware.action.didFailToRestoreOfficialDfu',
})); }));
/**
* Versions of official LEGO EV3 firmware available for restore.
*/
export enum EV3OfficialFirmwareVersion {
/** Official LEGO EV3 firmware version 1.09H (Home edition). */
home = '1.09H',
/** Official LEGO EV3 firmware version 1.09E (Education edition). */
education = '1.09E',
/** Official LEGO EV3 firmware version 1.10E (only useful for Microsoft MakeCode). */
makecode = '1.10E',
}
/**
* Action that triggers the restore official EV3 firmware saga.
*/
export const firmwareRestoreOfficialEV3 = createAction(
(version: EV3OfficialFirmwareVersion) => ({
type: 'firmware.action.restoreOfficialEV3',
version,
}),
);
/**
* Action that indicates {@link firmwareRestoreOfficialEV3} succeeded.
*/
export const firmwareDidRestoreOfficialEV3 = createAction(() => ({
type: 'firmware.action.didRestoreOfficialEV3',
}));
/**
* Action that indicates {@link firmwareRestoreOfficialEV3} failed.
*/
export const firmwareDidFailToRestoreOfficialEV3 = createAction(() => ({
type: 'firmware.action.didFailToRestoreOfficialEV3',
}));
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors // Copyright (c) 2022-2025 The Pybricks Authors
import './bootloaderInstructions.scss'; import './bootloaderInstructions.scss';
import { Callout, Intent } from '@blueprintjs/core'; import { Callout, Intent } from '@blueprintjs/core';
@@ -117,7 +117,11 @@ const BootloaderInstructions: React.FunctionComponent<BootloaderInstructionsProp
const { button, light, lightPattern } = useMemo(() => { const { button, light, lightPattern } = useMemo(() => {
return { return {
button: i18n.translate( button: i18n.translate(
hubHasBluetoothButton(hubType) ? 'button.bluetooth' : 'button.power', hubType === Hub.EV3
? 'button.right'
: hubHasBluetoothButton(hubType)
? 'button.bluetooth'
: 'button.power',
), ),
light: i18n.translate( light: i18n.translate(
hubHasBluetoothButton(hubType) ? 'light.bluetooth' : 'light.status', hubHasBluetoothButton(hubType) ? 'light.bluetooth' : 'light.status',
@@ -163,13 +167,16 @@ const BootloaderInstructions: React.FunctionComponent<BootloaderInstructionsProp
const prepareSteps = useMemo( const prepareSteps = useMemo(
() => ( () => (
<> <>
<li> {hubType !== Hub.EV3 && (
{i18n.translate( <li>
hubHasUSB(hubType) {i18n.translate(
? 'instructionGroup.prepare.usb' hubHasUSB(hubType)
: 'instructionGroup.prepare.batteries', ? 'instructionGroup.prepare.usb'
)} : 'instructionGroup.prepare.batteries',
</li> )}
</li>
)}
<li>{i18n.translate('instructionGroup.prepare.turnOff')}</li> <li>{i18n.translate('instructionGroup.prepare.turnOff')}</li>
{/* For non-usb recovery, show step about official app */} {/* For non-usb recovery, show step about official app */}
{recovery && !hubHasUSB(hubType) && ( {recovery && !hubHasUSB(hubType) && (
@@ -179,6 +186,12 @@ const BootloaderInstructions: React.FunctionComponent<BootloaderInstructionsProp
})} })}
</li> </li>
)} )}
{hubType === Hub.EV3 && (
<li>
{i18n.translate('instructionGroup.bootloaderMode.connectUsb')}
</li>
)}
</> </>
), ),
[i18n, recovery, hubType], [i18n, recovery, hubType],
@@ -210,7 +223,37 @@ const BootloaderInstructions: React.FunctionComponent<BootloaderInstructionsProp
{/* not strictly necessary, but order is swapped in the video, {/* not strictly necessary, but order is swapped in the video,
so we match it here. */} so we match it here. */}
{hubType !== Hub.Essential && hubHasUSB(hubType) && ( {(hubType === Hub.Prime || hubType === Hub.Inventor) &&
hubHasUSB(hubType) && (
<li
className={classNames(
activeStep === 'connect-usb' && 'pb-active-step',
)}
>
{i18n.translate(
'instructionGroup.bootloaderMode.connectUsb',
)}
</li>
)}
{hubType !== Hub.EV3 && (
<li
className={classNames(
activeStep === 'wait-for-light' && 'pb-active-step',
)}
>
{i18n.translate(
'instructionGroup.bootloaderMode.waitForLight',
{
button,
light,
lightPattern,
},
)}
</li>
)}
{hubType === Hub.Essential && hubHasUSB(hubType) && (
<li <li
className={classNames( className={classNames(
activeStep === 'connect-usb' && 'pb-active-step', activeStep === 'connect-usb' && 'pb-active-step',
@@ -220,25 +263,15 @@ const BootloaderInstructions: React.FunctionComponent<BootloaderInstructionsProp
</li> </li>
)} )}
<li {hubType === Hub.EV3 && (
className={classNames(
activeStep === 'wait-for-light' && 'pb-active-step',
)}
>
{i18n.translate('instructionGroup.bootloaderMode.waitForLight', {
button,
light,
lightPattern,
})}
</li>
{hubType === Hub.Essential && hubHasUSB(hubType) && (
<li <li
className={classNames( className={classNames(
activeStep === 'connect-usb' && 'pb-active-step', activeStep === 'press-power-button' && 'pb-active-step',
)} )}
> >
{i18n.translate('instructionGroup.bootloaderMode.connectUsb')} {i18n.translate(
'instructionGroup.bootloaderMode.pressPowerButtonEV3',
)}
</li> </li>
)} )}
@@ -262,7 +295,9 @@ const BootloaderInstructions: React.FunctionComponent<BootloaderInstructionsProp
)} )}
> >
{i18n.translate( {i18n.translate(
'instructionGroup.bootloaderMode.releaseButton', hubType === Hub.EV3
? 'instructionGroup.bootloaderMode.releaseButtonsEV3'
: 'instructionGroup.bootloaderMode.releaseButton',
{ {
button, button,
}, },
@@ -342,6 +377,13 @@ const BootloaderInstructions: React.FunctionComponent<BootloaderInstructionsProp
1 1
} }
> >
{hubType === Hub.EV3 && (
<li>
{i18n.translate(
'instructionGroup.connect.selectEV3FirmwareType',
)}
</li>
)}
<li> <li>
{i18n.translate( {i18n.translate(
'instructionGroup.connect.clickConnectAndFlash', 'instructionGroup.connect.clickConnectAndFlash',
@@ -389,7 +431,7 @@ const BootloaderInstructions: React.FunctionComponent<BootloaderInstructionsProp
</Callout> </Callout>
)} )}
{hubHasUSB(hubType) && isWindows() && ( {hubHasUSB(hubType) && hubType !== Hub.EV3 && isWindows() && (
<Callout intent={Intent.WARNING} icon={<WarningSign />}> <Callout intent={Intent.WARNING} icon={<WarningSign />}>
{i18n.translate('warning.windows.message', { {i18n.translate('warning.windows.message', {
instructions: ( instructions: (
@@ -11,7 +11,8 @@
}, },
"button": { "button": {
"bluetooth": "Bluetooth button", "bluetooth": "Bluetooth button",
"power": "button" "power": "button",
"right": "right button"
}, },
"light": { "light": {
"bluetooth": "Bluetooth light", "bluetooth": "Bluetooth light",
@@ -37,10 +38,13 @@
"waitForLight": "Wait for the {light} to start blinking {lightPattern}.", "waitForLight": "Wait for the {light} to start blinking {lightPattern}.",
"waitAppConnect": "The app will automatically connect and start restoring the firmware.", "waitAppConnect": "The app will automatically connect and start restoring the firmware.",
"releaseButton": "Release the {button}.", "releaseButton": "Release the {button}.",
"keepHolding": "Keep holding the {button} in the next steps. We'll tell you when to let go." "keepHolding": "Keep holding the {button} in the next steps. We'll tell you when to let go.",
"pressPowerButtonEV3": "Press the center button to turn on the EV3.",
"releaseButtonsEV3": "When the screen says \"Updating...\", release both buttons."
}, },
"connect": { "connect": {
"title": "Install:", "title": "Install:",
"selectEV3FirmwareType": "Select the version of firmware to restore below.",
"clickConnectAndFlash": "Click the {flashButton} button below.", "clickConnectAndFlash": "Click the {flashButton} button below.",
"selectDevice": "In the pop-up dialog, select {deviceName} and click {connectButton}.", "selectDevice": "In the pop-up dialog, select {deviceName} and click {connectButton}.",
"connectButton": "Connect" "connectButton": "Connect"
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors // Copyright (c) 2022-2025 The Pybricks Authors
import './installPybricksDialog.scss'; import './installPybricksDialog.scss';
import { import {
@@ -142,11 +142,6 @@ const UnsupportedHubs: React.FunctionComponent = () => {
'selectHubPanel.notOnListButton.info.mindstorms.nxt', 'selectHubPanel.notOnListButton.info.mindstorms.nxt',
)} )}
</li> </li>
<li>
{i18n.translate(
'selectHubPanel.notOnListButton.info.mindstorms.ev3',
)}
</li>
</ul> </ul>
<h4> <h4>
{i18n.translate('selectHubPanel.notOnListButton.info.poweredUp.title')} {i18n.translate('selectHubPanel.notOnListButton.info.poweredUp.title')}
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors // Copyright (c) 2022-2025 The Pybricks Authors
import { createAction } from '../../actions'; import { createAction } from '../../actions';
@@ -8,7 +8,7 @@ export const firmwareInstallPybricksDialogShow = createAction(() => ({
type: 'firmware.installPybricksDialog.action.show', type: 'firmware.installPybricksDialog.action.show',
})); }));
type FlashMethod = 'ble-lwp3-bootloader' | 'usb-lego-dfu'; type FlashMethod = 'ble-lwp3-bootloader' | 'usb-lego-dfu' | 'usb-ev3';
/** /**
* Action that indicates the user accepted the install Pybricks firmware dialog. * Action that indicates the user accepted the install Pybricks firmware dialog.
@@ -14,8 +14,7 @@
"sponsor": "sponsor" "sponsor": "sponsor"
}, },
"rcx": "RCX: Might work in streaming mode.", "rcx": "RCX: Might work in streaming mode.",
"nxt": "NXT: We have tested this. It could work!", "nxt": "NXT: We have tested this. It could work!"
"ev3": "EV3: Supported using Visual Studio Code."
}, },
"poweredUp": { "poweredUp": {
"title": "Other hubs", "title": "Other hubs",
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors // Copyright (c) 2022-2025 The Pybricks Authors
import { import {
Button, Button,
@@ -7,10 +7,13 @@ import {
DialogStep, DialogStep,
Intent, Intent,
MultistepDialog, MultistepDialog,
Radio,
RadioGroup,
} from '@blueprintjs/core'; } from '@blueprintjs/core';
import classNames from 'classnames'; import classNames from 'classnames';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { useLocalStorage } from 'usehooks-ts';
import { import {
legoEducationSpikeRegisteredTrademark, legoEducationSpikeRegisteredTrademark,
legoMindstormsRegisteredTrademark, legoMindstormsRegisteredTrademark,
@@ -20,7 +23,11 @@ import { Hub, hubHasUSB } from '../../components/hubPicker';
import { HubPicker } from '../../components/hubPicker/HubPicker'; import { HubPicker } from '../../components/hubPicker/HubPicker';
import { useHubPickerSelectedHub } from '../../components/hubPicker/hooks'; import { useHubPickerSelectedHub } from '../../components/hubPicker/hooks';
import { useSelector } from '../../reducers'; import { useSelector } from '../../reducers';
import { firmwareRestoreOfficialDfu } from '../actions'; import {
EV3OfficialFirmwareVersion,
firmwareRestoreOfficialDfu,
firmwareRestoreOfficialEV3,
} from '../actions';
import BootloaderInstructions from '../bootloaderInstructions/BootloaderInstructions'; import BootloaderInstructions from '../bootloaderInstructions/BootloaderInstructions';
import { firmwareRestoreOfficialDialogHide } from './actions'; import { firmwareRestoreOfficialDialogHide } from './actions';
import { useI18n } from './i18n'; import { useI18n } from './i18n';
@@ -51,11 +58,20 @@ const RestoreFirmwarePanel: React.FunctionComponent = () => {
s.firmware.isFirmwareFlashUsbDfuInProgress || s.firmware.isFirmwareFlashUsbDfuInProgress ||
s.firmware.isFirmwareRestoreOfficialDfuInProgress, s.firmware.isFirmwareRestoreOfficialDfuInProgress,
); );
const [ev3OfficialFirmwareVersion, setEv3OfficialFirmwareVersion] =
useLocalStorage<EV3OfficialFirmwareVersion>(
'ev3OfficialFirmwareVersion',
EV3OfficialFirmwareVersion.home,
);
const handleRestoreButtonClick = useCallback(() => { const handleRestoreDfuButtonClick = useCallback(() => {
dispatch(firmwareRestoreOfficialDfu(hubType)); dispatch(firmwareRestoreOfficialDfu(hubType));
}, [dispatch, hubType]); }, [dispatch, hubType]);
const handleRestoreEV3ButtonClick = useCallback(() => {
dispatch(firmwareRestoreOfficialEV3(ev3OfficialFirmwareVersion));
}, [dispatch, ev3OfficialFirmwareVersion]);
return ( return (
<div className={classNames(Classes.DIALOG_BODY, Classes.RUNNING_TEXT)}> <div className={classNames(Classes.DIALOG_BODY, Classes.RUNNING_TEXT)}>
<BootloaderInstructions <BootloaderInstructions
@@ -63,32 +79,103 @@ const RestoreFirmwarePanel: React.FunctionComponent = () => {
recovery recovery
flashButtonText={i18n.translate('restoreFirmwarePanel.flashButton')} flashButtonText={i18n.translate('restoreFirmwarePanel.flashButton')}
/> />
{hubHasUSB(hubType) ? ( {hubType === Hub.EV3 ? (
<> <div
<p> style={{
{i18n.translate('restoreFirmwarePanel.instruction2.updateApp', { display: 'flex',
app: flexDirection: 'row',
hubType === Hub.Inventor alignItems: 'center',
? legoMindstormsRegisteredTrademark justifyContent: 'space-evenly',
: legoEducationSpikeRegisteredTrademark, height: '100%',
})}{' '} }}
{hubType !== Hub.Inventor >
? i18n.translate(
'restoreFirmwarePanel.instruction2.updateAppVersion',
)
: ''}
</p>
<div className="pb-spacer" />
<Button <Button
intent={Intent.PRIMARY} intent={Intent.PRIMARY}
disabled={inProgress} disabled={inProgress}
onClick={handleRestoreButtonClick} onClick={handleRestoreEV3ButtonClick}
> >
{i18n.translate('restoreFirmwarePanel.flashButton')} {i18n.translate('restoreFirmwarePanel.flashButton')}
</Button> </Button>
</> <RadioGroup
selectedValue={ev3OfficialFirmwareVersion}
onChange={(event) =>
setEv3OfficialFirmwareVersion(
event.currentTarget.value as EV3OfficialFirmwareVersion,
)
}
>
<Radio
value={EV3OfficialFirmwareVersion.home}
checked={
ev3OfficialFirmwareVersion ===
EV3OfficialFirmwareVersion.home
}
>
{i18n.translate(
'restoreFirmwarePanel.ev3FirmwareType.home',
)}
</Radio>
<Radio
value={EV3OfficialFirmwareVersion.education}
checked={
ev3OfficialFirmwareVersion ===
EV3OfficialFirmwareVersion.education
}
>
{i18n.translate(
'restoreFirmwarePanel.ev3FirmwareType.education',
)}
</Radio>
<Radio
value={EV3OfficialFirmwareVersion.makecode}
checked={
ev3OfficialFirmwareVersion ===
EV3OfficialFirmwareVersion.makecode
}
>
{i18n.translate(
'restoreFirmwarePanel.ev3FirmwareType.makecode',
)}
</Radio>
</RadioGroup>
</div>
) : ( ) : (
<p>{i18n.translate('restoreFirmwarePanel.instruction2.ble.message')}</p> <>
{hubHasUSB(hubType) ? (
<>
<p>
{i18n.translate(
'restoreFirmwarePanel.instruction2.updateApp',
{
app:
hubType === Hub.Inventor
? legoMindstormsRegisteredTrademark
: legoEducationSpikeRegisteredTrademark,
},
)}{' '}
{hubType !== Hub.Inventor
? i18n.translate(
'restoreFirmwarePanel.instruction2.updateAppVersion',
)
: ''}
</p>
<div className="pb-spacer" />
<Button
intent={Intent.PRIMARY}
disabled={inProgress}
onClick={handleRestoreDfuButtonClick}
>
{i18n.translate('restoreFirmwarePanel.flashButton')}
</Button>
</>
) : (
<p>
{i18n.translate(
'restoreFirmwarePanel.instruction2.ble.message',
)}
</p>
)}
</>
)} )}
</div> </div>
); );
@@ -22,6 +22,11 @@
"updateApp": "Once the recovery is complete, use the {app} app to get the latest official firmware.", "updateApp": "Once the recovery is complete, use the {app} app to get the latest official firmware.",
"updateAppVersion": "You can use version 2 (legacy) or version 3 to do so." "updateAppVersion": "You can use version 2 (legacy) or version 3 to do so."
}, },
"flashButton": "Restore" "flashButton": "Restore",
"ev3FirmwareType": {
"home": "Home edition (v1.09H)",
"education": "Education edition (v1.09E)",
"makecode": "Microsoft MakeCode (v1.10E)"
}
} }
} }
+16 -1
View File
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// Copyright (c) 2020-2024 The Pybricks Authors // Copyright (c) 2020-2025 The Pybricks Authors
import { import {
FirmwareReader, FirmwareReader,
@@ -73,11 +73,13 @@ import {
didStart, didStart,
firmwareDidFailToFlashUsbDfu, firmwareDidFailToFlashUsbDfu,
firmwareDidFailToRestoreOfficialDfu, firmwareDidFailToRestoreOfficialDfu,
firmwareDidFailToRestoreOfficialEV3,
firmwareDidFlashUsbDfu, firmwareDidFlashUsbDfu,
firmwareDidRestoreOfficialDfu, firmwareDidRestoreOfficialDfu,
firmwareFlashUsbDfu, firmwareFlashUsbDfu,
firmwareInstallPybricks, firmwareInstallPybricks,
firmwareRestoreOfficialDfu, firmwareRestoreOfficialDfu,
firmwareRestoreOfficialEV3,
flashFirmware, flashFirmware,
} from './actions'; } from './actions';
import { firmwareDfuWindowsDriverInstallDialogDialogShow } from './dfuWindowsDriverInstallDialog/actions'; import { firmwareDfuWindowsDriverInstallDialogDialogShow } from './dfuWindowsDriverInstallDialog/actions';
@@ -912,6 +914,10 @@ function* handleInstallPybricks(): Generator {
); );
} }
break; break;
case 'usb-ev3':
// TODO: implement flashing via EV3 USB
console.error('Flashing via EV3 USB is not implemented yet');
break;
} }
} }
@@ -988,9 +994,18 @@ function* handleRestoreOfficialDfu(
} }
} }
function* handleRestoreOfficialEV3(
action: ReturnType<typeof firmwareRestoreOfficialEV3>,
): Generator {
action;
alert('Flashing via EV3 USB is not implemented yet');
yield* put(firmwareDidFailToRestoreOfficialEV3());
}
export default function* (): Generator { export default function* (): Generator {
yield* takeEvery(flashFirmware, handleFlashFirmware); yield* takeEvery(flashFirmware, handleFlashFirmware);
yield* takeEvery(firmwareFlashUsbDfu, handleFlashUsbDfu); yield* takeEvery(firmwareFlashUsbDfu, handleFlashUsbDfu);
yield* takeEvery(firmwareInstallPybricks, handleInstallPybricks); yield* takeEvery(firmwareInstallPybricks, handleInstallPybricks);
yield* takeEvery(firmwareRestoreOfficialDfu, handleRestoreOfficialDfu); yield* takeEvery(firmwareRestoreOfficialDfu, handleRestoreOfficialDfu);
yield* takeEvery(firmwareRestoreOfficialEV3, handleRestoreOfficialEV3);
} }
+5 -5
View File
@@ -2939,10 +2939,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@pybricks/images@npm:^1.3.0": "@pybricks/images@npm:^1.4.0":
version: 1.3.0 version: 1.4.0
resolution: "@pybricks/images@npm:1.3.0" resolution: "@pybricks/images@npm:1.4.0"
checksum: aad203588d967cd2dc3859226a4349992fec45f443b74a6b8e55f714c895fa25ee147c2e906b604bc48f93c48cd75526ec487a254ca142426d61ba29b7101d14 checksum: 644be26497cb648bebab6be5f7cc5dae28fbe210f1cb5435316d10411a9a4a016abc37fe6fb52c2dd06c2e45bcdb5c38db0a6fbb31690ee4ea6bfdacbc67c25c
languageName: node languageName: node
linkType: hard linkType: hard
@@ -2976,7 +2976,7 @@ __metadata:
"@pmmmwh/react-refresh-webpack-plugin": ^0.5.11 "@pmmmwh/react-refresh-webpack-plugin": ^0.5.11
"@pybricks/firmware": 7.22.0 "@pybricks/firmware": 7.22.0
"@pybricks/ide-docs": 2.20.0 "@pybricks/ide-docs": 2.20.0
"@pybricks/images": ^1.3.0 "@pybricks/images": ^1.4.0
"@pybricks/jedi": 1.17.0 "@pybricks/jedi": 1.17.0
"@pybricks/mpy-cross-v5": ^2.0.0 "@pybricks/mpy-cross-v5": ^2.0.0
"@pybricks/mpy-cross-v6": ^2.0.0 "@pybricks/mpy-cross-v6": ^2.0.0