Skip to content

Commit f0e44dd

Browse files
Handle bluetooth connection error scenarios (#279)
Only handles bluetooth connection error cases, not radio bridge
1 parent 86d4929 commit f0e44dd

13 files changed

+458
-204
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"@chakra-ui/react": "^2.8.2",
6262
"@emotion/react": "^11.11.4",
6363
"@emotion/styled": "^11.11.5",
64-
"@microbit/microbit-connection": "^0.0.0-alpha.13",
64+
"@microbit/microbit-connection": "^0.0.0-alpha.14",
6565
"@tensorflow/tfjs": "^4.4.0",
6666
"@types/w3c-web-serial": "^1.0.6",
6767
"@types/w3c-web-usb": "^1.0.6",

src/App.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { GesturesProvider } from "./gestures-hooks";
2525
import { MlStatusProvider } from "./ml-status-hooks";
2626
import { ConnectionStageProvider } from "./connection-stage-hooks";
2727
import { ConnectProvider } from "./connect-actions-hooks";
28+
import { ConnectStatusProvider } from "./connect-status-hooks";
2829

2930
export interface ProviderLayoutProps {
3031
children: ReactNode;
@@ -43,13 +44,15 @@ const Providers = ({ children }: ProviderLayoutProps) => {
4344
<SettingsProvider>
4445
<GesturesProvider>
4546
<MlStatusProvider>
46-
<ConnectProvider>
47-
<ConnectionStageProvider>
48-
<TranslationProvider>
49-
<ErrorBoundary>{children}</ErrorBoundary>
50-
</TranslationProvider>
51-
</ConnectionStageProvider>
52-
</ConnectProvider>
47+
<ConnectStatusProvider>
48+
<ConnectProvider>
49+
<ConnectionStageProvider>
50+
<TranslationProvider>
51+
<ErrorBoundary>{children}</ErrorBoundary>
52+
</TranslationProvider>
53+
</ConnectionStageProvider>
54+
</ConnectProvider>
55+
</ConnectStatusProvider>
5356
</MlStatusProvider>
5457
</GesturesProvider>
5558
</SettingsProvider>

src/components/ConnectCableDialog.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Image, Text, VStack } from "@chakra-ui/react";
1+
import { Button, Image, Text, VStack } from "@chakra-ui/react";
22
import { FormattedMessage } from "react-intl";
33
import connectCableImage from "../images/connect-cable.gif";
44
import ConnectContainerDialog, {
@@ -51,17 +51,23 @@ const ConnectCableDialog = ({
5151
onSwitch,
5252
...props
5353
}: ConnectCableDialogProps) => {
54-
const { subtitleId, onLink, ...typeProps } = configs[type];
54+
const { subtitleId, onLink, linkTextId, headingId } = configs[type];
5555
const linkConfig = {
5656
[LinkType.None]: undefined,
5757
[LinkType.Skip]: onSkip,
5858
[LinkType.Switch]: onSwitch,
5959
};
6060
return (
6161
<ConnectContainerDialog
62-
{...typeProps}
62+
headingId={headingId}
6363
{...props}
64-
onLinkClick={linkConfig[onLink]}
64+
footerLeft={
65+
linkTextId && (
66+
<Button onClick={linkConfig[onLink]} variant="link" size="lg">
67+
<FormattedMessage id={linkTextId} />
68+
</Button>
69+
)
70+
}
6571
>
6672
<VStack gap={5}>
6773
<Text width="100%">

src/components/ConnectContainerDialog.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ export interface ConnectContainerDialogProps {
1919
isOpen: boolean;
2020
onClose: () => void;
2121
headingId: string;
22-
onLinkClick?: () => void;
23-
linkTextId?: string;
22+
footerLeft?: ReactNode;
2423
onNextClick?: () => void;
2524
children: ReactNode;
2625
onBackClick?: () => void;
@@ -30,8 +29,7 @@ const ConnectContainerDialog = ({
3029
isOpen,
3130
onClose,
3231
headingId,
33-
onLinkClick,
34-
linkTextId,
32+
footerLeft,
3533
onNextClick,
3634
onBackClick,
3735
children,
@@ -57,14 +55,10 @@ const ConnectContainerDialog = ({
5755
</VStack>
5856
</ModalBody>
5957
<ModalFooter
60-
justifyContent={onLinkClick && linkTextId ? "space-between" : "end"}
58+
justifyContent={footerLeft ? "space-between" : "end"}
6159
p={0}
6260
>
63-
{onLinkClick && linkTextId && (
64-
<Button onClick={onLinkClick} variant="link" size="lg">
65-
<FormattedMessage id={linkTextId} />
66-
</Button>
67-
)}
61+
{footerLeft && footerLeft}
6862
<HStack gap={5}>
6963
{onBackClick && (
7064
<Button onClick={onBackClick} variant="secondary" size="lg">

src/components/ConnectionFlowDialogs.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,8 @@ const ConnectionDialogs = () => {
235235
case ConnectionFlowStep.WebUsbBluetoothUnsupported: {
236236
return <WebUsbBluetoothUnsupportedDialog {...dialogCommonProps} />;
237237
}
238-
// TODO: Reconnect dialogs
239-
case ConnectionFlowStep.ReconnectManualFail:
240-
case ConnectionFlowStep.ReconnectAutoFail: {
238+
case ConnectionFlowStep.ReconnectFailed:
239+
case ConnectionFlowStep.ConnectionLost: {
241240
return (
242241
<ReconnectErrorDialog
243242
{...dialogCommonProps}

src/components/LiveGraphPanel.tsx

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,30 @@ import { Button, HStack, Portal, Text } from "@chakra-ui/react";
22
import { useMemo, useRef } from "react";
33
import { MdBolt } from "react-icons/md";
44
import { FormattedMessage } from "react-intl";
5-
import {
6-
ConnectionStatus,
7-
useConnectionStage,
8-
} from "../connection-stage-hooks";
5+
import { useConnectionStage } from "../connection-stage-hooks";
96
import InfoToolTip from "./InfoToolTip";
107
import LiveGraph from "./LiveGraph";
8+
import { ConnectionStatus, useConnectStatus } from "../connect-status-hooks";
119

1210
const LiveGraphPanel = () => {
1311
const { actions } = useConnectionStage();
14-
const { stage } = useConnectionStage();
12+
const status = useConnectStatus();
1513
const parentPortalRef = useRef(null);
1614

17-
const connectBtnConfig = useMemo(
18-
() =>
19-
stage.status === ConnectionStatus.None ||
20-
stage.status === ConnectionStatus.Connecting
21-
? {
22-
textId: "footer.connectButton",
23-
onClick: actions.start,
24-
}
25-
: {
26-
textId: "actions.reconnect",
27-
onClick: actions.reconnect,
28-
},
29-
[actions.reconnect, actions.start, stage]
30-
);
15+
const connectBtnConfig = useMemo(() => {
16+
return status === ConnectionStatus.NotConnected ||
17+
status === ConnectionStatus.Connecting ||
18+
status === ConnectionStatus.FailedToConnect ||
19+
status === ConnectionStatus.FailedToReconnectTwice
20+
? {
21+
textId: "footer.connectButton",
22+
onClick: actions.start,
23+
}
24+
: {
25+
textId: "actions.reconnect",
26+
onClick: actions.reconnect,
27+
};
28+
}, [actions.reconnect, actions.start, status]);
3129

3230
return (
3331
<HStack
@@ -49,7 +47,7 @@ const LiveGraphPanel = () => {
4947
>
5048
<HStack gap={4}>
5149
<LiveIndicator />
52-
{stage.status === ConnectionStatus.Connected ? (
50+
{status === ConnectionStatus.Connected ? (
5351
<Button variant="primary" size="sm" onClick={actions.disconnect}>
5452
<FormattedMessage id="footer.disconnectButton" />
5553
</Button>
@@ -58,15 +56,15 @@ const LiveGraphPanel = () => {
5856
variant="primary"
5957
size="sm"
6058
isDisabled={
61-
stage.status === ConnectionStatus.Reconnecting ||
62-
stage.status === ConnectionStatus.Connecting
59+
status === ConnectionStatus.Reconnecting ||
60+
status === ConnectionStatus.Connecting
6361
}
6462
onClick={connectBtnConfig.onClick}
6563
>
6664
<FormattedMessage id={connectBtnConfig.textId} />
6765
</Button>
6866
)}
69-
{stage.status === ConnectionStatus.Reconnecting && (
67+
{status === ConnectionStatus.Reconnecting && (
7068
<Text rounded="4xl" bg="white" py="1px" fontWeight="bold">
7169
<FormattedMessage id="connectMB.reconnecting" />
7270
</Text>

src/components/ReconnectErrorDialog.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ interface ReconnectErrorDialogProps {
2626
onReconnect: () => void;
2727
flowType: ConnectionFlowType;
2828
errorStep:
29-
| ConnectionFlowStep.ReconnectManualFail
30-
| ConnectionFlowStep.ReconnectAutoFail;
29+
| ConnectionFlowStep.ReconnectFailed
30+
| ConnectionFlowStep.ConnectionLost;
3131
}
3232

3333
const contentConfig = {
@@ -55,8 +55,8 @@ const contentConfig = {
5555
};
5656

5757
const errorTextIdPrefixConfig = {
58-
[ConnectionFlowStep.ReconnectAutoFail]: "disconnectedWarning",
59-
[ConnectionFlowStep.ReconnectManualFail]: "reconnectFailed",
58+
[ConnectionFlowStep.ConnectionLost]: "disconnectedWarning",
59+
[ConnectionFlowStep.ReconnectFailed]: "reconnectFailed",
6060
};
6161

6262
const ReconnectErrorDialog = ({

src/components/WhatYouWillNeedDialog.tsx

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Grid, GridItem, Image, Text, VStack } from "@chakra-ui/react";
1+
import { Button, Grid, GridItem, Image, Text, VStack } from "@chakra-ui/react";
22
import { FormattedMessage } from "react-intl";
33
import batteryPackImage from "../images/stylised-battery-pack.svg";
44
import microbitImage from "../images/stylised-microbit-black.svg";
@@ -9,6 +9,7 @@ import computerBluetoothImage from "../images/stylised_computer_w_bluetooth.svg"
99
import ConnectContainerDialog, {
1010
ConnectContainerDialogProps,
1111
} from "./ConnectContainerDialog";
12+
import ExternalLink from "./ExternalLink";
1213

1314
const itemsConfig = {
1415
radio: [
@@ -60,24 +61,42 @@ export interface WhatYouWillNeedDialogProps
6061
> {
6162
reconnect: boolean;
6263
type: "radio" | "bluetooth";
64+
onLinkClick: (() => void) | undefined;
6365
}
6466

6567
const WhatYouWillNeedDialog = ({
6668
reconnect,
6769
type,
70+
onLinkClick,
6871
...props
6972
}: WhatYouWillNeedDialogProps) => {
7073
return (
7174
<ConnectContainerDialog
7275
{...props}
73-
linkTextId={`connectMB.${type}Start.switch${
74-
type === "bluetooth" ? "Radio" : "Bluetooth"
75-
}`}
7676
headingId={
7777
reconnect
78-
? `connectMB.${type}Start.heading`
78+
? `reconnectFailed.${type}Heading`
7979
: `connectMB.${type}Start.heading`
8080
}
81+
footerLeft={
82+
<VStack alignItems="start">
83+
{onLinkClick && (
84+
<Button onClick={onLinkClick} variant="link" size="lg">
85+
<FormattedMessage
86+
id={`connectMB.${type}Start.switch${
87+
type === "bluetooth" ? "Radio" : "Bluetooth"
88+
}`}
89+
/>
90+
</Button>
91+
)}
92+
{reconnect && (
93+
<ExternalLink
94+
href="https://support.microbit.org/a/solutions/articles/19000157495"
95+
textId="connectMB.troubleshoot"
96+
/>
97+
)}
98+
</VStack>
99+
}
81100
>
82101
{reconnect && (
83102
<Text>

src/connect-actions.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
AccelerometerDataEvent,
3+
ConnectionStatusEvent,
34
ButtonEvent,
45
ConnectionStatus as DeviceConnectionStatus,
56
DeviceError,
@@ -78,7 +79,7 @@ export class ConnectActions {
7879
try {
7980
await this.usb.flash(data, {
8081
partial: true,
81-
progress: (v) => progress(v ?? 100),
82+
progress: (v: number | undefined) => progress(v ?? 100),
8283
});
8384
return ConnectAndFlashResult.Success;
8485
} catch (e) {
@@ -118,16 +119,16 @@ export class ConnectActions {
118119
};
119120

120121
connectBluetooth = async (
121-
name: string | undefined
122-
): Promise<ConnectResult> => {
122+
name: string | undefined,
123+
clearDevice: boolean
124+
): Promise<void> => {
125+
if (clearDevice) {
126+
await this.bluetooth.clearDevice();
127+
}
123128
if (name) {
124129
this.bluetooth.setNameFilter(name);
125130
}
126131
await this.bluetooth.connect();
127-
if (this.bluetooth.status === DeviceConnectionStatus.CONNECTED) {
128-
return ConnectResult.Success;
129-
}
130-
return ConnectResult.ManualConnectFailed;
131132
};
132133

133134
addAccelerometerListener = (
@@ -166,4 +167,12 @@ export class ConnectActions {
166167
await this.bluetooth.disconnect();
167168
await this.radioBridge.disconnect();
168169
};
170+
171+
addStatusListener = (listener: (e: ConnectionStatusEvent) => void) => {
172+
this.bluetooth.addEventListener("status", listener);
173+
};
174+
175+
removeStatusListener = (listener: (e: ConnectionStatusEvent) => void) => {
176+
this.bluetooth.removeEventListener("status", listener);
177+
};
169178
}

0 commit comments

Comments
 (0)