mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-15 11:04:49 +00:00
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:
+1
-1
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user