Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ jobs:
- uses: actions/checkout@v3
- run: yarn install
- run: yarn coverage
- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v5
with:
directory: coverage
2 changes: 1 addition & 1 deletion config/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ module.exports = function (webpackEnv) {
// Bump up the default maximum size (2mb) that's precached,
// to make lazy-loading failure scenarios less likely.
// See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270
maximumFileSizeToCacheInBytes: 10 * 1024 * 1024,
maximumFileSizeToCacheInBytes: 16 * 1024 * 1024,
additionalManifestEntries: [
// FIXME: this path should have a hash
{ url: 'static/oss-licenses.json', revision: null },
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@pybricks/firmware": "7.22.0",
"@pybricks/ide-docs": "2.20.0",
"@pybricks/images": "^1.3.0",
"@pybricks/images": "^1.4.0",
"@pybricks/jedi": "1.17.0",
"@pybricks/mpy-cross-v5": "^2.0.0",
"@pybricks/mpy-cross-v6": "^2.0.0",
Expand All @@ -35,6 +35,7 @@
"@types/react-transition-group": "^4.4.10",
"@types/redux-logger": "^3.0.12",
"@types/semver": "^7.5.6",
"@types/w3c-web-hid": "^1.0.6",
"@types/w3c-web-usb": "^1.0.10",
"@types/web-bluetooth": "^0.0.20",
"@types/web-locks-api": "^0.0.5",
Expand Down
8 changes: 7 additions & 1 deletion src/components/hubPicker/HubPicker.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022-2023 The Pybricks Authors
// Copyright (c) 2022-2025 The Pybricks Authors

import './hubPicker.scss';
import { Radio, RadioGroup } from '@blueprintjs/core';
Expand Down Expand Up @@ -66,6 +66,12 @@ export const HubPicker: React.FunctionComponent<HubPickerProps> = ({ disabled })
label="MINDSTORMS Robot Inventor Hub"
/>
</Radio>
<Radio value={Hub.EV3}>
<HubIcon
url={new URL('@pybricks/images/hub-ev3.png', import.meta.url)}
label="MINDSTORMS EV3"
/>
</Radio>
</RadioGroup>
);
};
8 changes: 7 additions & 1 deletion src/components/hubPicker/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
// Copyright (c) 2022-2025 The Pybricks Authors

/** Supported hub types. */
export enum Hub {
Expand All @@ -15,6 +15,8 @@ export enum Hub {
Prime = 'primehub',
/** SPIKE Essential hub */
Essential = 'essentialhub',
/** MINDSTORMS EV3 hub */
EV3 = 'ev3',
}

/**
Expand All @@ -25,6 +27,7 @@ export function hubHasUSB(hub: Hub): boolean {
case Hub.Prime:
case Hub.Essential:
case Hub.Inventor:
case Hub.EV3:
return true;
default:
return false;
Expand Down Expand Up @@ -52,6 +55,7 @@ export function hubHasExternalFlash(hub: Hub): boolean {
case Hub.Prime:
case Hub.Essential:
case Hub.Inventor:
case Hub.EV3:
return true;
default:
return false;
Expand All @@ -69,5 +73,7 @@ export function hubBootloaderType(hub: Hub) {
case Hub.City:
case Hub.Technic:
return 'ble-lwp3-bootloader';
case Hub.EV3:
return 'usb-ev3';
}
}
19 changes: 17 additions & 2 deletions src/editor/pybricksMicroPython.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,9 @@ export const language = <monaco.languages.IMonarchLanguage>{
*/
function createTemplate(hubClassName: string, deviceClassNames: string[]): string {
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.robotics import DriveBase
from pybricks.tools import wait, StopWatch
Expand All @@ -325,7 +327,8 @@ type HubLabel =
| 'technichub'
| 'inventorhub'
| 'primehub'
| 'essentialhub';
| 'essentialhub'
| 'ev3';

const templateSnippets: Array<
Required<
Expand Down Expand Up @@ -375,6 +378,18 @@ const templateSnippets: Array<
'ColorLightMatrix',
]),
},
{
label: 'ev3',
documentation: 'Template for MINDSTORMS EV3 program.',
insertText: createTemplate('EV3Brick', [
'Motor',
'ColorSensor',
'GyroSensor',
'InfraredSensor',
'TouchSensor',
'UltrasonicSensor',
]),
},
];

/**
Expand Down
80 changes: 79 additions & 1 deletion src/firmware/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// 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 { createAction } from '../actions';
Expand Down Expand Up @@ -429,3 +429,81 @@ export const firmwareDidRestoreOfficialDfu = createAction(() => ({
export const firmwareDidFailToRestoreOfficialDfu = createAction(() => ({
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',
}));

/**
* Low-level action to flash firmware to an EV3 hub.
* @param firmware The firmware binary blob.
*/
export const firmwareFlashEV3 = createAction((firmware: ArrayBuffer) => ({
type: 'firmware.action.flashEV3',
firmware,
}));

/**
* Low-level action that indicates {@link firmwareFlashEV3} succeeded.
*/
export const firmwareDidFlashEV3 = createAction(() => ({
type: 'firmware.action.didFlashEV3',
}));

/**
* Low-level action that indicates {@link firmwareFlashEV3} failed.
*/
export const firmwareDidFailToFlashEV3 = createAction(() => ({
type: 'firmware.action.didFailToFlashEV3',
}));

export const firmwareDidReceiveEV3Reply = createAction(
(
length: number,
replyNumber: number,
messageType: number,
replyCommand: number,
status: number,
payload: ArrayBufferLike,
) => ({
type: 'firmware.action.didReceiveEV3Reply',
length,
replyNumber,
messageType,
replyCommand,
status,
payload,
}),
);
25 changes: 25 additions & 0 deletions src/firmware/alerts/NoWebHid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2025 The Pybricks Authors

import { Intent } from '@blueprintjs/core';
import { Error } from '@blueprintjs/icons';
import React from 'react';
import type { CreateToast } from '../../toasterTypes';
import { useI18n } from './i18n';

const NoWebHid: React.FunctionComponent = () => {
const i18n = useI18n();
return (
<>
<p>{i18n.translate('noWebHid.message')}</p>
<p>{i18n.translate('noWebHid.suggestion')}</p>
</>
);
};

export const noWebHid: CreateToast = (onAction) => ({
message: <NoWebHid />,
icon: <Error />,
intent: Intent.DANGER,
onDismiss: () => onAction('dismiss'),
});
4 changes: 3 additions & 1 deletion src/firmware/alerts/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2022 The Pybricks Authors
// Copyright (c) 2022-2025 The Pybricks Authors

import { dfuError } from './DfuError';
import { flashProgress } from './FlashProgress';
import { noDfuHub } from './NoDfuHub';
import { noDfuInterface } from './NoDfuInterface';
import { noWebHid } from './NoWebHid';
import { noWebUsb } from './NoWebUsb';
import { releaseButton } from './ReleaseButton';

Expand All @@ -13,6 +14,7 @@ export default {
flashProgress,
noDfuHub,
noDfuInterface,
noWebHid,
noWebUsb,
releaseButton,
};
6 changes: 5 additions & 1 deletion src/firmware/alerts/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@
"tryAgainButton": "Try again"
},
"noWebUsb": {
"message": "This browser does not support Web USB or Web USB is not enabled.",
"message": "This browser does not support WebUSB or WebUSB is not enabled.",
"suggestion": "Use a supported browser such as Google Chrome or Microsoft Edge."
},
"noWebHid": {
"message": "This browser does not support WebHID or WebHID is not enabled.",
"suggestion": "Use a supported browser such as Google Chrome or Microsoft Edge."
},
"noDfuHub": {
Expand Down
Binary file added src/firmware/assets/EV3_Firmware_V1.09H.bin
Binary file not shown.
Binary file added src/firmware/assets/ev3-image-1.10e.bin
Binary file not shown.
Binary file added src/firmware/assets/ev3_firmware_v1.09e.bin
Binary file not shown.
Loading
Loading