diff --git a/src/app/constants.ts b/src/app/constants.ts
index b6639a30..1870b11d 100644
--- a/src/app/constants.ts
+++ b/src/app/constants.ts
@@ -51,9 +51,6 @@ export const pybricksUsbDfuWindowsDriverInstallUrl =
export const pybricksUsbLinuxUdevRulesUrl =
'https://github.com/pybricks/support/discussions/688#discussioncomment-3239099';
-export const pybricksBleFirmwareRestoreVideoUrl =
- 'https://pybricks.com/install/technic-boost-city/#restoring-the-original-firmware';
-
/** Pybricks copyright statement. */
export const pybricksCopyright = 'Copyright (c) 2020-2023 The Pybricks Authors';
diff --git a/src/firmware/bootloaderInstructions/BootloaderInstructions.tsx b/src/firmware/bootloaderInstructions/BootloaderInstructions.tsx
index 87db2f4f..706f1cce 100644
--- a/src/firmware/bootloaderInstructions/BootloaderInstructions.tsx
+++ b/src/firmware/bootloaderInstructions/BootloaderInstructions.tsx
@@ -6,6 +6,7 @@ import { Callout, Intent } from '@blueprintjs/core';
import classNames from 'classnames';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
+ legoRegisteredTrademark,
pybricksUsbDfuWindowsDriverInstallUrl,
pybricksUsbLinuxUdevRulesUrl,
} from '../../app/constants';
@@ -24,10 +25,24 @@ import primeHubMp4 from './assets/bootloader-primehub-540.mp4';
import primeHubVtt from './assets/bootloader-primehub-metadata.vtt';
import technicHubMp4 from './assets/bootloader-technichub-540.mp4';
import technicHubVtt from './assets/bootloader-technichub-metadata.vtt';
+import cityHubRecoveryMp4 from './assets/recover-cityhub-540.mp4';
+import cityHubRecoveryVtt from './assets/recover-cityhub-metadata.vtt';
+import moveHubRecoveryMp4 from './assets/recover-movehub-540.mp4';
+import moveHubRecoveryVtt from './assets/recover-movehub-metadata.vtt';
+import technicHubRecoveryMp4 from './assets/recover-technichub-540.mp4';
+import technicHubRecoveryVtt from './assets/recover-technichub-metadata.vtt';
import { useI18n } from './i18n';
type BootloaderInstructionsProps = {
+ /**
+ * The instructions and video will be customized for this hub.
+ */
hubType: Hub;
+ /**
+ * If true, show official firmware recovery video and steps for supported hubs.
+ * @default false
+ */
+ recovery?: boolean;
};
const videoFileMap: ReadonlyMap = new Map([
@@ -48,13 +63,30 @@ const metadataFileMap: ReadonlyMap = new Map([
[Hub.Technic, technicHubVtt],
]);
+const recoveryVideoFileMap: ReadonlyMap = new Map([
+ [Hub.City, cityHubRecoveryMp4],
+ [Hub.Essential, essentialHubMp4],
+ [Hub.Inventor, inventorHubMp4],
+ [Hub.Move, moveHubRecoveryMp4],
+ [Hub.Prime, primeHubMp4],
+ [Hub.Technic, technicHubRecoveryMp4],
+]);
+
+const recoveryMetadataFileMap: ReadonlyMap = new Map([
+ [Hub.City, cityHubRecoveryVtt],
+ [Hub.Essential, essentialHubVtt],
+ [Hub.Inventor, inventorHubVtt],
+ [Hub.Move, moveHubRecoveryVtt],
+ [Hub.Prime, primeHubVtt],
+ [Hub.Technic, technicHubRecoveryVtt],
+]);
/**
* Provides customized instructions on how to enter bootloader mode based
* on the hub type.
*/
const BootloaderInstructions: React.VoidFunctionComponent<
BootloaderInstructionsProps
-> = ({ hubType }) => {
+> = ({ hubType, recovery }) => {
const i18n = useI18n();
const { button, light, lightPattern } = useMemo(() => {
@@ -139,10 +171,21 @@ const BootloaderInstructions: React.VoidFunctionComponent<
disablePictureInPicture
className="pb-bootloader-video"
>
-
+
@@ -153,6 +196,10 @@ const BootloaderInstructions: React.VoidFunctionComponent<
{i18n.translate('instruction', {
startPoweredOff: hubHasUSB(hubType)
? i18n.translate('startPoweredOff.usb')
+ : recovery
+ ? i18n.translate('startPoweredOff.recovery', {
+ lego: legoRegisteredTrademark,
+ })
: i18n.translate('startPoweredOff.default'),
})}
@@ -210,8 +257,18 @@ const BootloaderInstructions: React.VoidFunctionComponent<
)}
+ {recovery && !hubHasUSB(hubType) && (
+
+ {i18n.translate('step.waitAppConnect')}
+
+ )}
+
{/* hubs with USB will keep the power on, but other hubs won't */}
- {hubHasUSB(hubType) ? (
+ {recovery || hubHasUSB(hubType) ? (
00:07.000
+
+hold-button
+00:07.000 --> 00:11.000
+
+wait-for-light
+00:11.000 --> 00:19.000
+
+wait-app-connect
+00:19.000 --> 00:25.000
+
+release-button
+00:25.000 --> 00:28.000
diff --git a/src/firmware/bootloaderInstructions/assets/recover-movehub-540.mp4 b/src/firmware/bootloaderInstructions/assets/recover-movehub-540.mp4
new file mode 100644
index 00000000..8d893483
Binary files /dev/null and b/src/firmware/bootloaderInstructions/assets/recover-movehub-540.mp4 differ
diff --git a/src/firmware/bootloaderInstructions/assets/recover-movehub-metadata.vtt b/src/firmware/bootloaderInstructions/assets/recover-movehub-metadata.vtt
new file mode 100644
index 00000000..574c78a7
--- /dev/null
+++ b/src/firmware/bootloaderInstructions/assets/recover-movehub-metadata.vtt
@@ -0,0 +1,13 @@
+WEBVTT
+
+hold-button
+00:00.100 --> 00:05.000
+
+wait-for-light
+00:05.000 --> 00:11.000
+
+wait-app-connect
+00:11.000 --> 00:15.000
+
+release-button
+00:15.000 --> 00:18.000
diff --git a/src/firmware/bootloaderInstructions/assets/recover-technichub-540.mp4 b/src/firmware/bootloaderInstructions/assets/recover-technichub-540.mp4
new file mode 100644
index 00000000..7c727160
Binary files /dev/null and b/src/firmware/bootloaderInstructions/assets/recover-technichub-540.mp4 differ
diff --git a/src/firmware/bootloaderInstructions/assets/recover-technichub-metadata.vtt b/src/firmware/bootloaderInstructions/assets/recover-technichub-metadata.vtt
new file mode 100644
index 00000000..12a5eda5
--- /dev/null
+++ b/src/firmware/bootloaderInstructions/assets/recover-technichub-metadata.vtt
@@ -0,0 +1,13 @@
+WEBVTT
+
+hold-button
+00:05.000 --> 00:08.000
+
+wait-for-light
+00:08.000 --> 00:14.000
+
+wait-app-connect
+00:14.000 --> 00:21.000
+
+release-button
+00:21.000 --> 00:24.000
diff --git a/src/firmware/bootloaderInstructions/bootloaderInstructions.scss b/src/firmware/bootloaderInstructions/bootloaderInstructions.scss
index 540b4dfb..1bfe0556 100644
--- a/src/firmware/bootloaderInstructions/bootloaderInstructions.scss
+++ b/src/firmware/bootloaderInstructions/bootloaderInstructions.scss
@@ -8,5 +8,6 @@
.pb-bootloader-video {
width: 90%;
- align-self: center;
+ display: block;
+ margin: 0 auto;
}
diff --git a/src/firmware/bootloaderInstructions/translations/en.json b/src/firmware/bootloaderInstructions/translations/en.json
index f9cc8a58..5113176f 100644
--- a/src/firmware/bootloaderInstructions/translations/en.json
+++ b/src/firmware/bootloaderInstructions/translations/en.json
@@ -7,7 +7,8 @@
"instruction": "To flash the firmware, the hub must be placed in bootloader mode. {startPoweredOff}, then follow the instructions below:",
"startPoweredOff": {
"default": "Start with the hub powered off",
- "usb": "Start with the hub powered off and the USB cable disconnected"
+ "usb": "Start with the hub powered off and the USB cable disconnected",
+ "recovery": "Start with the hub powered off and the {lego} PoweredUp app opened to any 'Create' program"
},
"button": {
"bluetooth": "Bluetooth button",
@@ -26,7 +27,8 @@
"holdButton": "Press and hold the {button} on the hub.",
"connectUsb": "Connect the USB cable.",
"waitForLight": "Keep holding the {button} and wait for the {light} to start flashing {lightPattern}. This takes about 5 seconds.",
- "releaseButton": "Release the {button}",
+ "waitAppConnect": "The app will automatically connect and start flashing the firmware.",
+ "releaseButton": "Release the {button}.",
"keepHolding": "Keep holding the {button}."
}
}
diff --git a/src/firmware/restoreOfficialDialog/RestoreOfficialDialog.tsx b/src/firmware/restoreOfficialDialog/RestoreOfficialDialog.tsx
index 8989a40f..a2d1f344 100644
--- a/src/firmware/restoreOfficialDialog/RestoreOfficialDialog.tsx
+++ b/src/firmware/restoreOfficialDialog/RestoreOfficialDialog.tsx
@@ -11,11 +11,7 @@ import {
import classNames from 'classnames';
import React from 'react';
import { useDispatch } from 'react-redux';
-import {
- legoRegisteredTrademark,
- pybricksBleFirmwareRestoreVideoUrl,
-} from '../../app/constants';
-import ExternalLinkIcon from '../../components/ExternalLinkIcon';
+import { legoRegisteredTrademark } from '../../app/constants';
import { hubHasUSB } from '../../components/hubPicker';
import { HubPicker } from '../../components/hubPicker/HubPicker';
import { useHubPickerSelectedHub } from '../../components/hubPicker/hooks';
@@ -51,7 +47,7 @@ const RestoreFirmwarePanel: React.VoidFunctionComponent = () => {
return (
-
+
{hubHasUSB(hubType) ? (
<>
@@ -73,25 +69,7 @@ const RestoreFirmwarePanel: React.VoidFunctionComponent = () => {
>
) : (
-
- {i18n.translate('restoreFirmwarePanel.instruction2.ble.message', {
- lego: legoRegisteredTrademark,
- thisVideo: (
- <>
-
- {i18n.translate(
- 'restoreFirmwarePanel.instruction2.ble.thisVideo',
- )}
-
-
- >
- ),
- })}
-
+
{i18n.translate('restoreFirmwarePanel.instruction2.ble.message')}
)}
);
diff --git a/src/firmware/restoreOfficialDialog/translations/en.json b/src/firmware/restoreOfficialDialog/translations/en.json
index 95727a3e..1a091da9 100644
--- a/src/firmware/restoreOfficialDialog/translations/en.json
+++ b/src/firmware/restoreOfficialDialog/translations/en.json
@@ -12,8 +12,7 @@
"title": "Restore firmware",
"instruction2": {
"ble": {
- "message": "Then use the official {lego} app for this hub to connect to the hub and restore the firmware. Watch {thisVideo} to see how.",
- "thisVideo": "this video"
+ "message": "When the progress bar is done, the hub will reboot and the app will connect. The official firmware has been restored."
},
"dfu": "Then click the {flashFirmware} button below to connect to the hub and flash the firmware. After flashing is complete, connect the hub to one of the official {lego} apps and use it to update to the latest official firmware."
},