mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
Package firmware in app
- includes firmware .zip for movehub and cplushub - firmware flash button no longer opens file dialog - now connects first, then choses firmware based on connected device - drag and drop of custom firmware still works
This commit is contained in:
committed by
David Lechner
parent
5160d29c7e
commit
33861d970b
@@ -3,6 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@pybricks/firmware": "^1.0.0",
|
||||
"@pybricks/mpy-cross-v4": "^1.0.0",
|
||||
"@testing-library/jest-dom": "^5.8.0",
|
||||
"@testing-library/react": "^10.0.4",
|
||||
|
||||
@@ -428,12 +428,20 @@ export enum BootloaderActionType {
|
||||
FlashProgress = 'bootloader.action.flash.progress',
|
||||
}
|
||||
|
||||
/**
|
||||
* Action that flashes firmware to a hub.
|
||||
*/
|
||||
export interface BootloaderFlashFirmwareAction
|
||||
extends Action<BootloaderActionType.FlashFirmware> {
|
||||
data: ArrayBuffer;
|
||||
/** The firmware zip file data or undefined to get firmware later. */
|
||||
data?: ArrayBuffer;
|
||||
}
|
||||
|
||||
export function flashFirmware(data: ArrayBuffer): BootloaderFlashFirmwareAction {
|
||||
/**
|
||||
* Creates a new action to flash firmware to a hub.
|
||||
* @param data The firmware zip file data or undefined to get firmware later.
|
||||
*/
|
||||
export function flashFirmware(data?: ArrayBuffer): BootloaderFlashFirmwareAction {
|
||||
return { type: BootloaderActionType.FlashFirmware, data };
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import { BootloaderConnectionState } from '../reducers/bootloader';
|
||||
import OpenFileButton, { OpenFileButtonProps } from './OpenFileButton';
|
||||
|
||||
type StateProps = Pick<OpenFileButtonProps, 'enabled'>;
|
||||
type DispatchProps = Pick<OpenFileButtonProps, 'onFile' | 'onReject'>;
|
||||
type DispatchProps = Pick<OpenFileButtonProps, 'onFile' | 'onReject' | 'onClick'>;
|
||||
type OwnProps = Pick<OpenFileButtonProps, 'id'>;
|
||||
|
||||
const mapStateToProps = (state: RootState): StateProps => ({
|
||||
@@ -26,6 +26,9 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({
|
||||
notification.add('error', `'${file.name}' is not a valid firmware file.`),
|
||||
);
|
||||
},
|
||||
onClick: (): void => {
|
||||
dispatch(flashFirmware());
|
||||
},
|
||||
});
|
||||
|
||||
const mergeProps = (
|
||||
|
||||
@@ -23,6 +23,8 @@ export interface OpenFileButtonProps {
|
||||
readonly onFile: (data: ArrayBuffer) => void;
|
||||
/** Callback that is called when a file has been rejected (e.g. bad file extension). */
|
||||
readonly onReject: (file: File) => void;
|
||||
/** If defined, will call custom function instead of opening file browser. */
|
||||
readonly onClick?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,6 +72,8 @@ class OpenFileButton extends React.Component<OpenFileButtonProps> {
|
||||
onDropRejected={this.onDropRejected}
|
||||
accept={this.props.fileExtension}
|
||||
multiple={false}
|
||||
noClick={this.props.onClick !== undefined}
|
||||
noKeyboard={this.props.onClick !== undefined}
|
||||
>
|
||||
{({ getRootProps, getInputProps }): JSX.Element => (
|
||||
<OverlayTrigger
|
||||
@@ -89,6 +93,12 @@ class OpenFileButton extends React.Component<OpenFileButtonProps> {
|
||||
? { pointerEvents: 'none' }
|
||||
: undefined
|
||||
}
|
||||
// onClick={this.props.onClick}
|
||||
// breaks Dropzone when this.props.onClick is undefined
|
||||
// so we have to do it the long way
|
||||
{...(this.props.onClick
|
||||
? { onClick: this.props.onClick }
|
||||
: {})}
|
||||
>
|
||||
<input {...getInputProps()} />
|
||||
<Image
|
||||
|
||||
Vendored
+5
@@ -1 +1,6 @@
|
||||
/// <reference types="react-scripts" />
|
||||
|
||||
declare module '*.zip' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
+60
-8
@@ -1,6 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020 The Pybricks Authors
|
||||
|
||||
import cPlusHubZip from '@pybricks/firmware/build/cplushub.zip';
|
||||
import moveHubZip from '@pybricks/firmware/build/movehub.zip';
|
||||
import JSZip from 'jszip';
|
||||
import { Action } from 'redux';
|
||||
import { Channel, buffers } from 'redux-saga';
|
||||
@@ -82,6 +84,11 @@ import {
|
||||
} from '../protocols/bootloader';
|
||||
import { fmod, sumComplement32 } from '../utils/math';
|
||||
|
||||
const firmwareZipMap = new Map<HubType, string>([
|
||||
[HubType.CPlusHub, cPlusHubZip],
|
||||
[HubType.MoveHub, moveHubZip],
|
||||
]);
|
||||
|
||||
/**
|
||||
* Converts a request action into bytecodes and creates a new action to send
|
||||
* the bytecodes to to the device.
|
||||
@@ -227,11 +234,13 @@ function* firmwareIterator(data: DataView, maxSize: number): Generator<number> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Flashes firmware to a Powered Up device.
|
||||
* @param action The action that triggered this saga.
|
||||
* Loads Pybricks firmware from a .zip file
|
||||
* @param data The zip file raw data
|
||||
*/
|
||||
function* flashFirmware(action: BootloaderFlashFirmwareAction): Generator {
|
||||
const zip = (yield call(() => JSZip().loadAsync(action.data))) as JSZip;
|
||||
function* loadFirmware(
|
||||
data: ArrayBuffer,
|
||||
): Generator<unknown, { firmware: Uint8Array; deviceId: HubType }> {
|
||||
const zip = (yield call(() => JSZip.loadAsync(data))) as JSZip;
|
||||
const firmwareBase = (yield call(() =>
|
||||
zip.file('firmware-base.bin').async('uint8array'),
|
||||
)) as Uint8Array;
|
||||
@@ -279,6 +288,21 @@ function* flashFirmware(action: BootloaderFlashFirmwareAction): Generator {
|
||||
true,
|
||||
);
|
||||
|
||||
return { firmware, deviceId: metadata['device-id'] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Flashes firmware to a Powered Up device.
|
||||
* @param action The action that triggered this saga.
|
||||
*/
|
||||
function* flashFirmware(action: BootloaderFlashFirmwareAction): Generator {
|
||||
let firmware: Uint8Array | undefined = undefined;
|
||||
let deviceId: HubType | undefined = undefined;
|
||||
|
||||
if (action.data !== undefined) {
|
||||
({ firmware, deviceId } = yield* loadFirmware(action.data));
|
||||
}
|
||||
|
||||
yield put(connect());
|
||||
const didConnect = (yield take([
|
||||
BootloaderConnectionActionType.DidConnect,
|
||||
@@ -306,10 +330,38 @@ function* flashFirmware(action: BootloaderFlashFirmwareAction): Generator {
|
||||
throw Error(`failed to get info: ${info}`);
|
||||
}
|
||||
|
||||
if (info[0].hubType !== metadata['device-id']) {
|
||||
throw Error(
|
||||
`Connected to ${info[0].hubType} but firmware is for ${metadata['device-id']}`,
|
||||
);
|
||||
if (deviceId !== undefined && info[0].hubType !== deviceId) {
|
||||
throw Error(`Connected to ${info[0].hubType} but firmware is for ${deviceId}`);
|
||||
}
|
||||
|
||||
if (firmware === undefined) {
|
||||
const firmwarePath = firmwareZipMap.get(info[0].hubType);
|
||||
if (firmwarePath === undefined) {
|
||||
yield put(
|
||||
notification.add(
|
||||
'error',
|
||||
"Sorry, we don't have firmware for this hub yet.",
|
||||
),
|
||||
);
|
||||
yield put(disconnectRequest());
|
||||
return;
|
||||
}
|
||||
|
||||
const response = (yield call(() => fetch(firmwarePath))) as Response;
|
||||
if (!response.ok) {
|
||||
yield put(notification.add('error', 'Failed to fetch firmware.'));
|
||||
yield put(disconnectRequest());
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (yield call(() => response.arrayBuffer())) as ArrayBuffer;
|
||||
({ firmware, deviceId } = yield* loadFirmware(data));
|
||||
|
||||
if (deviceId !== undefined && info[0].hubType !== deviceId) {
|
||||
throw Error(
|
||||
`Connected to ${info[0].hubType} but firmware is for ${deviceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// City hub bootloader is buggy. See note in encodeRequest().
|
||||
|
||||
@@ -1254,6 +1254,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.0.tgz#0e1bdf8d021e7ea58affade33d9d607e11365915"
|
||||
integrity sha512-NMrDy6EWh9TPdSRiHmHH2ye1v5U0gBD7pRYwSwJvomx7Bm4GG04vu63dYiVzebLOx2obPpJugew06xVP0Nk7hA==
|
||||
|
||||
"@pybricks/firmware@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://npm.pkg.github.com/download/@pybricks/firmware/1.0.0/dfc52164caa622a8c5e0c59d110319441b7966f25e4d2764d592ce7b56672465#3f763dff5751d8e4b8ff94f73678ee30ac52233b"
|
||||
integrity sha512-eMGuzLRKVMvnZc093/7aW5/fGtRfz39aSLy+Drhc6j4tJqySBdRhNk709mH7ZYMj4RJ2D6mWNbuzi3LVGsVkAA==
|
||||
|
||||
"@pybricks/mpy-cross-v4@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://npm.pkg.github.com/download/@pybricks/mpy-cross-v4/1.0.0/1961a986e77b302cb05c3e6dc03feefe06292488a05d23ea32d27e2f625e782a#82633377519ba904dbd3e0f700a4b38f8212a80f"
|
||||
|
||||
Reference in New Issue
Block a user