Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.

Commit 861d894

Browse files
authored
Merge pull request #525 from smalruby/feature/microbit-more-firmware-update
feat: MicrobitMore拡張機能のファームウェア更新機能の実装
2 parents 00f3528 + 7907165 commit 861d894

13 files changed

Lines changed: 361 additions & 24 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ npm-*
2323

2424
# Downloaded during "npm install"
2525
/static/microbit
26+
/static/microbitMore
2627

2728
# for act
2829
.secrets

scripts/prepublish.mjs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,44 @@ const downloadMicrobitHex = async () => {
110110
console.info(`Wrote ${relativeGeneratedFile}`);
111111
};
112112

113+
const downloadMicrobitMoreHex = async () => {
114+
const url = 'https://github.com/microbit-more/pxt-mbit-more-v2/releases/download/0.2.5/microbit-mbit-more-v2-0_2_5.hex';
115+
console.info(`Downloading ${url}`);
116+
const response = await crossFetch(url);
117+
const hexBuffer = Buffer.from(await response.arrayBuffer());
118+
const relativeHexDir = path.join('static', 'microbitMore');
119+
const hexFileName = 'microbit-mbit-more-v2-0_2_5.hex';
120+
const relativeHexFile = path.join(relativeHexDir, hexFileName);
121+
const absoluteDestDir = path.join(basePath, relativeHexDir);
122+
fs.mkdirSync(absoluteDestDir, {recursive: true});
123+
const absoluteDestFile = path.join(basePath, relativeHexFile);
124+
fs.writeFileSync(absoluteDestFile, hexBuffer);
125+
126+
const relativeGeneratedDir = path.join('src', 'generated');
127+
const relativeGeneratedFile = path.join(relativeGeneratedDir, 'microbit-more-hex-url.cjs');
128+
const absoluteGeneratedDir = path.join(basePath, relativeGeneratedDir);
129+
fs.mkdirSync(absoluteGeneratedDir, {recursive: true});
130+
const absoluteGeneratedFile = path.join(basePath, relativeGeneratedFile);
131+
const requirePath = `./${path
132+
.relative(relativeGeneratedDir, relativeHexFile)
133+
.split(path.win32.sep)
134+
.join(path.posix.sep)}`;
135+
fs.writeFileSync(
136+
absoluteGeneratedFile,
137+
[
138+
'// This file is generated by scripts/prepublish.mjs',
139+
'// Do not edit this file directly',
140+
'// This file relies on a loader to turn this `require` into a URL',
141+
`module.exports = require('${requirePath}');`,
142+
'' // final newline
143+
].join('\n')
144+
);
145+
console.info(`Wrote ${relativeGeneratedFile}`);
146+
};
147+
113148
const prepublish = async () => {
114149
await downloadMicrobitHex();
150+
await downloadMicrobitMoreHex();
115151
};
116152

117153
prepublish().then(

src/components/connection-modal/connection-modal.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,8 @@
446446
height: 60%;
447447
width: inherit;
448448
}
449+
450+
.important-instruction {
451+
font-weight: bold;
452+
color: $ui-red;
453+
}

src/components/connection-modal/connection-modal.jsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,20 @@ const ConnectionModalComponent = props => (
3939
{props.phase === PHASES.scanning && props.useAutoScan && <AutoScanningStep {...props} />}
4040
{props.phase === PHASES.connecting && <ConnectingStep {...props} />}
4141
{props.phase === PHASES.connected && <ConnectedStep {...props} />}
42-
{props.phase === PHASES.error && <ErrorStep {...props} />}
43-
{props.phase === PHASES.unavailable && <UnavailableStep {...props} />}
42+
{props.phase === PHASES.error && (
43+
<ErrorStep
44+
{...props}
45+
onScanning={props.onScanning}
46+
onUpdatePeripheral={props.onUpdatePeripheral}
47+
/>
48+
)}
49+
{props.phase === PHASES.unavailable && (
50+
<UnavailableStep
51+
{...props}
52+
onScanning={props.onScanning}
53+
onUpdatePeripheral={props.onUpdatePeripheral}
54+
/>
55+
)}
4456
{props.phase === PHASES.updatePeripheral && <UpdatePeripheralStep {...props} />}
4557
</Box>
4658
</Modal>
@@ -55,6 +67,8 @@ ConnectionModalComponent.propTypes = {
5567
name: PropTypes.node,
5668
onCancel: PropTypes.func.isRequired,
5769
onHelp: PropTypes.func.isRequired,
70+
onScanning: PropTypes.func,
71+
onUpdatePeripheral: PropTypes.func,
5872
phase: PropTypes.oneOf(Object.keys(PHASES)).isRequired,
5973
title: PropTypes.string.isRequired,
6074
useAutoScan: PropTypes.bool.isRequired

src/components/connection-modal/error-step.jsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Box from '../box/box.jsx';
77
import Dots from './dots.jsx';
88
import helpIcon from './icons/help.svg';
99
import backIcon from './icons/back.svg';
10+
import enterUpdateIcon from './icons/enter-update.svg';
1011

1112
import styles from './connection-modal.css';
1213

@@ -30,6 +31,15 @@ const ErrorStep = props => (
3031
id="gui.connection.error.errorMessage"
3132
/>
3233
</div>
34+
{props.onUpdatePeripheral && (
35+
<div className={classNames(styles.bottomAreaItem, styles.instructions)}>
36+
<FormattedMessage
37+
defaultMessage="If you don't see your device, you may need to update it to work with Scratch."
38+
description="Prompt for updating a peripheral device"
39+
id="gui.connection.scanning.updatePeripheralPrompt"
40+
/>
41+
</div>
42+
)}
3343
<Dots
3444
error
3545
className={styles.bottomAreaItem}
@@ -50,6 +60,22 @@ const ErrorStep = props => (
5060
id="gui.connection.error.tryagainbutton"
5161
/>
5262
</button>
63+
{props.onUpdatePeripheral && (
64+
<button
65+
className={styles.connectionButton}
66+
onClick={props.onUpdatePeripheral}
67+
>
68+
<FormattedMessage
69+
defaultMessage="Update my Device"
70+
description="Button to enter the peripheral update mode"
71+
id="gui.connection.scanning.updatePeripheralButton"
72+
/>
73+
<img
74+
className={styles.buttonIconRight}
75+
src={enterUpdateIcon}
76+
/>
77+
</button>
78+
)}
5379
<button
5480
className={styles.connectionButton}
5581
onClick={props.onHelp}
@@ -72,7 +98,8 @@ const ErrorStep = props => (
7298
ErrorStep.propTypes = {
7399
connectionIconURL: PropTypes.string.isRequired,
74100
onHelp: PropTypes.func,
75-
onScanning: PropTypes.func
101+
onScanning: PropTypes.func,
102+
onUpdatePeripheral: PropTypes.func
76103
};
77104

78105
export default ErrorStep;

src/components/connection-modal/unavailable-step.jsx

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import backIcon from './icons/back.svg';
1010
import bluetoothIcon from './icons/bluetooth.svg';
1111
import scratchLinkIcon from './icons/scratchlink.svg';
1212

13+
import enterUpdateIcon from './icons/enter-update.svg';
14+
1315
import styles from './connection-modal.css';
1416

1517
const UnavailableStep = props => (
@@ -55,6 +57,15 @@ const UnavailableStep = props => (
5557
</div>
5658
</Box>
5759
<Box className={styles.bottomArea}>
60+
<Box className={classNames(styles.bottomAreaItem, styles.instructions)}>
61+
{props.onUpdatePeripheral && (
62+
<FormattedMessage
63+
defaultMessage="If you don't see your device, you may need to update it to work with Scratch."
64+
description="Prompt for updating a peripheral device"
65+
id="gui.connection.scanning.updatePeripheralPrompt"
66+
/>
67+
)}
68+
</Box>
5869
<Dots
5970
error
6071
className={styles.bottomAreaItem}
@@ -75,6 +86,22 @@ const UnavailableStep = props => (
7586
id="gui.connection.unavailable.tryagainbutton"
7687
/>
7788
</button>
89+
{props.onUpdatePeripheral && (
90+
<button
91+
className={styles.connectionButton}
92+
onClick={props.onUpdatePeripheral}
93+
>
94+
<FormattedMessage
95+
defaultMessage="Update my Device"
96+
description="Button to enter the peripheral update mode"
97+
id="gui.connection.scanning.updatePeripheralButton"
98+
/>
99+
<img
100+
className={styles.buttonIconRight}
101+
src={enterUpdateIcon}
102+
/>
103+
</button>
104+
)}
78105
<button
79106
className={styles.connectionButton}
80107
onClick={props.onHelp}
@@ -96,7 +123,8 @@ const UnavailableStep = props => (
96123

97124
UnavailableStep.propTypes = {
98125
onHelp: PropTypes.func,
99-
onScanning: PropTypes.func
126+
onScanning: PropTypes.func,
127+
onUpdatePeripheral: PropTypes.func
100128
};
101129

102130
export default UnavailableStep;

src/components/connection-modal/update-peripheral-step.jsx

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,36 @@ class UpdatePeripheralStep extends React.Component {
136136
renderResults () {
137137
let resultsContent;
138138
if (this.state.err === null) {
139-
resultsContent = (<FormattedMessage
140-
defaultMessage="Update successful!"
141-
description="Message to indicate that the peripheral update was successful"
142-
id="gui.connection.updatePeripheral.updateSuccessful"
143-
/>);
139+
if (this.props.extensionId === 'microbitMore') {
140+
resultsContent = (
141+
<Box className={styles.scratchLinkError}>
142+
<Box className={styles.centeredRow}>
143+
<FormattedMessage
144+
defaultMessage="MicrobitMore update successful!"
145+
description="Message to indicate that the MicrobitMore update was successful"
146+
id="gui.connection.updatePeripheral.microbitMoreUpdateSuccessful"
147+
/>
148+
</Box>
149+
<Box className={classNames(styles.centeredRow, styles.importantInstruction)}>
150+
<FormattedMessage
151+
defaultMessage="Tilt your micro:bit to light up all 25 LEDs to complete."
152+
description="Instructions to tilt the micro:bit to complete the update process"
153+
id="gui.connection.updatePeripheral.microbitMoreTiltToLightUp"
154+
/>
155+
</Box>
156+
</Box>
157+
);
158+
} else {
159+
resultsContent = (
160+
<Box className={styles.centeredRow}>
161+
<FormattedMessage
162+
defaultMessage="Update successful!"
163+
description="Message to indicate that the peripheral update was successful"
164+
id="gui.connection.updatePeripheral.updateSuccessful"
165+
/>
166+
</Box>
167+
);
168+
}
144169
} else if (this.state.err.message === 'No valid interfaces found.') {
145170
// this is a special case where the micro:bit's communication firmware is too old to support WebUSB
146171
resultsContent = (<BalancedFormattedMessage
@@ -160,12 +185,13 @@ class UpdatePeripheralStep extends React.Component {
160185
} else {
161186
resultsContent = (
162187
<Box className={styles.scratchLinkError}>
163-
<FormattedMessage
164-
className={styles.centeredRow}
165-
defaultMessage="Update failed."
166-
description="Message to indicate that the peripheral update failed"
167-
id="gui.connection.updatePeripheral.updateFailed"
168-
/>
188+
<Box className={styles.centeredRow}>
189+
<FormattedMessage
190+
defaultMessage="Update failed."
191+
description="Message to indicate that the peripheral update failed"
192+
id="gui.connection.updatePeripheral.updateFailed"
193+
/>
194+
</Box>
169195
<textarea
170196
className={styles.scratchLinkErrorDetails}
171197
readOnly
@@ -254,6 +280,7 @@ class UpdatePeripheralStep extends React.Component {
254280

255281
UpdatePeripheralStep.propTypes = {
256282
connectionSmallIconURL: PropTypes.string,
283+
extensionId: PropTypes.string,
257284
name: PropTypes.string.isRequired,
258285
onScanning: PropTypes.func.isRequired,
259286
onSendPeripheralUpdate: PropTypes.func.isRequired

src/containers/connection-modal.jsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import {connect} from 'react-redux';
99

1010
import {closeConnectionModal} from '../reducers/modals';
1111
import {isMicroBitUpdateSupported, selectAndUpdateMicroBit} from '../lib/microbit-update';
12+
import {
13+
isMicroBitUpdateSupported as isMicroBitMoreUpdateSupported,
14+
selectAndUpdateMicroBit as selectAndUpdateMicroBitMore
15+
} from '../lib/microbit-more-update';
1216

1317
class ConnectionModal extends React.Component {
1418
constructor (props) {
@@ -141,11 +145,15 @@ class ConnectionModal extends React.Component {
141145
label: this.props.extensionId
142146
});
143147

144-
// TODO: get this functionality from the extension
148+
if (this.props.extensionId === 'microbitMore') {
149+
return selectAndUpdateMicroBitMore(progressCallback);
150+
}
145151
return selectAndUpdateMicroBit(progressCallback);
146152
}
147153
render () {
148-
const canUpdatePeripheral = (this.props.extensionId === 'microbit') && isMicroBitUpdateSupported();
154+
const canUpdatePeripheral =
155+
(this.props.extensionId === 'microbit' && isMicroBitUpdateSupported()) ||
156+
(this.props.extensionId === 'microbitMore' && isMicroBitMoreUpdateSupported());
149157
return (
150158
<ConnectionModalComponent
151159
connectingMessage={this.state.extension && this.state.extension.connectingMessage}

src/css/colors.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ $ui-black-transparent-10: hsla(0, 0%, 0%, 0.10); /* 10% transparent version of b
1515
$ui-green: hsla(163, 85%, 35%, 1); /* #0DA57A */
1616
$ui-green-2: hsla(163, 85%, 40%, 1); /* #0FBD8C */
1717

18+
$ui-red: hsla(0, 100%, 50%, 1); /* #FF0000 */
19+
1820
$text-primary: hsla(225, 15%, 40%, 1); /* #575E75 */
1921
$text-primary-transparent: hsla(225, 15%, 40%, 0.75);
2022

0 commit comments

Comments
 (0)