Skip to content

Commit 70bedec

Browse files
committed
firmware: implement EV3 official firmware restore
Basic support for restoring the official EV3 firmware. Is missing a video and needs a bunch of helpful error messages, but basic functionality is working.
1 parent 007640c commit 70bedec

File tree

15 files changed

+446
-10
lines changed

15 files changed

+446
-10
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@types/react-transition-group": "^4.4.10",
3636
"@types/redux-logger": "^3.0.12",
3737
"@types/semver": "^7.5.6",
38+
"@types/w3c-web-hid": "^1.0.6",
3839
"@types/w3c-web-usb": "^1.0.10",
3940
"@types/web-bluetooth": "^0.0.20",
4041
"@types/web-locks-api": "^0.0.5",

src/firmware/actions.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,3 +465,45 @@ export const firmwareDidRestoreOfficialEV3 = createAction(() => ({
465465
export const firmwareDidFailToRestoreOfficialEV3 = createAction(() => ({
466466
type: 'firmware.action.didFailToRestoreOfficialEV3',
467467
}));
468+
469+
/**
470+
* Low-level action to flash firmware to an EV3 hub.
471+
* @param firmware The firmware binary blob.
472+
*/
473+
export const firmwareFlashEV3 = createAction((firmware: ArrayBuffer) => ({
474+
type: 'firmware.action.flashEV3',
475+
firmware,
476+
}));
477+
478+
/**
479+
* Low-level action that indicates {@link firmwareFlashEV3} succeeded.
480+
*/
481+
export const firmwareDidFlashEV3 = createAction(() => ({
482+
type: 'firmware.action.didFlashEV3',
483+
}));
484+
485+
/**
486+
* Low-level action that indicates {@link firmwareFlashEV3} failed.
487+
*/
488+
export const firmwareDidFailToFlashEV3 = createAction(() => ({
489+
type: 'firmware.action.didFailToFlashEV3',
490+
}));
491+
492+
export const firmwareDidReceiveEV3Reply = createAction(
493+
(
494+
length: number,
495+
replyNumber: number,
496+
messageType: number,
497+
replyCommand: number,
498+
status: number,
499+
payload: ArrayBufferLike,
500+
) => ({
501+
type: 'firmware.action.didReceiveEV3Reply',
502+
length,
503+
replyNumber,
504+
messageType,
505+
replyCommand,
506+
status,
507+
payload,
508+
}),
509+
);

src/firmware/alerts/NoWebHid.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: MIT
2+
// Copyright (c) 2025 The Pybricks Authors
3+
4+
import { Intent } from '@blueprintjs/core';
5+
import { Error } from '@blueprintjs/icons';
6+
import React from 'react';
7+
import type { CreateToast } from '../../toasterTypes';
8+
import { useI18n } from './i18n';
9+
10+
const NoWebHid: React.FunctionComponent = () => {
11+
const i18n = useI18n();
12+
return (
13+
<>
14+
<p>{i18n.translate('noWebHid.message')}</p>
15+
<p>{i18n.translate('noWebHid.suggestion')}</p>
16+
</>
17+
);
18+
};
19+
20+
export const noWebHid: CreateToast = (onAction) => ({
21+
message: <NoWebHid />,
22+
icon: <Error />,
23+
intent: Intent.DANGER,
24+
onDismiss: () => onAction('dismiss'),
25+
});

src/firmware/alerts/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
// SPDX-License-Identifier: MIT
2-
// Copyright (c) 2022 The Pybricks Authors
2+
// Copyright (c) 2022-2025 The Pybricks Authors
33

44
import { dfuError } from './DfuError';
55
import { flashProgress } from './FlashProgress';
66
import { noDfuHub } from './NoDfuHub';
77
import { noDfuInterface } from './NoDfuInterface';
8+
import { noWebHid } from './NoWebHid';
89
import { noWebUsb } from './NoWebUsb';
910
import { releaseButton } from './ReleaseButton';
1011

@@ -13,6 +14,7 @@ export default {
1314
flashProgress,
1415
noDfuHub,
1516
noDfuInterface,
17+
noWebHid,
1618
noWebUsb,
1719
releaseButton,
1820
};

src/firmware/alerts/translations/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
"message": "This browser does not support WebUSB or WebUSB is not enabled.",
99
"suggestion": "Use a supported browser such as Google Chrome or Microsoft Edge."
1010
},
11+
"noWebHid": {
12+
"message": "This browser does not support WebHID or WebHID is not enabled.",
13+
"suggestion": "Use a supported browser such as Google Chrome or Microsoft Edge."
14+
},
1115
"noDfuHub": {
1216
"message": "Could not find your hub?",
1317
"suggestion1": {
15.6 MB
Binary file not shown.
15.6 MB
Binary file not shown.
15.6 MB
Binary file not shown.

src/firmware/installPybricksDialog/InstallPybricksDialog.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,8 @@ export const InstallPybricksDialog: React.FunctionComponent = () => {
453453
const inProgress = useSelector(
454454
(s) =>
455455
s.firmware.isFirmwareFlashUsbDfuInProgress ||
456-
s.firmware.isFirmwareRestoreOfficialDfuInProgress,
456+
s.firmware.isFirmwareRestoreOfficialDfuInProgress ||
457+
s.firmware.isFirmwareFlashEV3InProgress,
457458
);
458459
const dispatch = useDispatch();
459460
const [hubName, setHubName] = useState('');

src/firmware/reducers.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// SPDX-License-Identifier: MIT
2-
// Copyright (c) 2021-2023 The Pybricks Authors
2+
// Copyright (c) 2021-2025 The Pybricks Authors
33

44
import { AnyAction } from 'redux';
55
import {
@@ -23,6 +23,7 @@ test('initial state', () => {
2323
"installPybricksDialog": {
2424
"isOpen": false,
2525
},
26+
"isFirmwareFlashEV3InProgress": false,
2627
"isFirmwareFlashUsbDfuInProgress": false,
2728
"isFirmwareRestoreOfficialDfuInProgress": false,
2829
"progress": null,

0 commit comments

Comments
 (0)