diff --git a/CHANGELOG.md b/CHANGELOG.md index f5c93c91..c6aba64f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ ## [Unreleased] +### Changed +- Firmware restore for hubs with USB is now done in-app ([pybricks-code#1104]). + +[pybricks-code#1104]: https://github.com/pybricks/pybricks-code/issues/1104 + ### Fixed - Fixed missing warning sign icon. diff --git a/src/firmware/assets/COPYING.md b/src/firmware/assets/COPYING.md new file mode 100644 index 00000000..f8df1df4 --- /dev/null +++ b/src/firmware/assets/COPYING.md @@ -0,0 +1,3 @@ +Official LEGO firmware is redistributed with permission for the express purpose +of restoring the firmware on LEGO hardware. Any other use is governed by the +LEGO EULA in the respective official LEGO apps. diff --git a/src/firmware/assets/essential-v1.0.00.0071-191f3ad.bin b/src/firmware/assets/essential-v1.0.00.0071-191f3ad.bin new file mode 100644 index 00000000..ac50a6e0 Binary files /dev/null and b/src/firmware/assets/essential-v1.0.00.0071-191f3ad.bin differ diff --git a/src/firmware/assets/prime-v1.3.00.0000-e8c274a.bin b/src/firmware/assets/prime-v1.3.00.0000-e8c274a.bin new file mode 100644 index 00000000..8f764b26 Binary files /dev/null and b/src/firmware/assets/prime-v1.3.00.0000-e8c274a.bin differ diff --git a/src/firmware/sagas.ts b/src/firmware/sagas.ts index da6b6d02..51ffbcc1 100644 --- a/src/firmware/sagas.ts +++ b/src/firmware/sagas.ts @@ -34,6 +34,7 @@ import { alertsHideAlert, alertsShowAlert, } from '../alerts/actions'; +import { Hub } from '../components/hubPicker'; import { checksumRequest, checksumResponse, @@ -72,9 +73,12 @@ import { didProgress, didStart, firmwareDidFailToFlashUsbDfu, + firmwareDidFailToRestoreOfficialDfu, firmwareDidFlashUsbDfu, + firmwareDidRestoreOfficialDfu, firmwareFlashUsbDfu, firmwareInstallPybricks, + firmwareRestoreOfficialDfu, flashFirmware, } from './actions'; import { @@ -899,8 +903,82 @@ function* handleInstallPybricks(): Generator { } } +function getUrlForHubType(hub: Hub): URL { + switch (hub) { + case Hub.Prime: + case Hub.Inventor: + return new URL('./assets/prime-v1.3.00.0000-e8c274a.bin', import.meta.url); + case Hub.Essential: + return new URL( + './assets/essential-v1.0.00.0071-191f3ad.bin', + import.meta.url, + ); + default: + throw new Error(`unsupported hub: ${hub}`); + } +} + +function getHubTypeForHub(hub: Hub): HubType { + switch (hub) { + case Hub.Prime: + case Hub.Inventor: + return HubType.PrimeHub; + case Hub.Essential: + return HubType.EssentialHub; + default: + throw new Error(`unsupported hub: ${hub}`); + } +} + +function* handleRestoreOfficialDfu( + action: ReturnType, +): Generator { + try { + const url = getUrlForHubType(action.hub); + + const response = yield* call(() => fetch(url)); + + if (!response.ok) { + // TODO: replace with proper alert + // istanbul ignore if + if (process.env.NODE_ENV !== 'test') { + console.error(response); + } + throw new Error('failed to fetch'); + } + + const firmwareBlob = yield* call(() => response.blob()); + const firmware = yield* call(() => firmwareBlob.arrayBuffer()); + + yield* put(firmwareFlashUsbDfu(firmware, getHubTypeForHub(action.hub))); + + const { didFailToFlash } = yield* race({ + didFlash: take(firmwareDidFlashUsbDfu), + didFailToFlash: take(firmwareDidFailToFlashUsbDfu), + }); + + if (didFailToFlash) { + yield* put(firmwareDidFailToRestoreOfficialDfu()); + return; + } + + yield* put(firmwareDidRestoreOfficialDfu()); + } catch (err) { + // istanbul ignore if + if (process.env.NODE_ENV !== 'test') { + console.error(err); + } + + yield* put( + alertsShowAlert('alerts', 'unexpectedError', { error: ensureError(err) }), + ); + yield* put(firmwareDidFailToRestoreOfficialDfu()); + } +} + export default function* (): Generator { yield* takeEvery(flashFirmware, handleFlashFirmware); yield* takeEvery(firmwareFlashUsbDfu, handleFlashUsbDfu); yield* takeEvery(firmwareInstallPybricks, handleInstallPybricks); + yield* takeEvery(firmwareRestoreOfficialDfu, handleRestoreOfficialDfu); }