Skip to content

Handle bluetooth connecting, reconnecting, success, and failures #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions lib/bluetooth-device-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ class ServiceInfo<T extends Service> {
}
}

interface ConnectCallbacks {
onConnecting: () => void;
onReconnecting: () => void;
onFail: () => void;
onSuccess: () => void;
}

export class BluetoothDeviceWrapper {
// Used to avoid automatic reconnection during user triggered connect/disconnect
// or reconnection itself.
Expand Down Expand Up @@ -136,6 +143,7 @@ export class BluetoothDeviceWrapper {
private dispatchTypedEvent: TypedServiceEventDispatcher,
// We recreate this for the same connection and need to re-setup notifications for the old events
private events: Record<keyof ServiceConnectionEventMap, boolean>,
private callbacks: ConnectCallbacks,
) {
device.addEventListener(
"gattserverdisconnected",
Expand All @@ -158,6 +166,11 @@ export class BluetoothDeviceWrapper {
await this.gattConnectPromise;
return;
}
if (this.isReconnect) {
this.callbacks.onReconnecting();
} else {
this.callbacks.onConnecting();
}
this.duringExplicitConnectDisconnect++;
if (this.device.gatt === undefined) {
throw new Error(
Expand Down Expand Up @@ -233,16 +246,20 @@ export class BluetoothDeviceWrapper {
type: this.isReconnect ? "Reconnect" : "Connect",
message: "Bluetooth connect success",
});
this.callbacks.onSuccess();
} catch (e) {
this.logging.error("Bluetooth connect error", e);
this.logging.event({
type: this.isReconnect ? "Reconnect" : "Connect",
message: "Bluetooth connect failed",
});
await this.disconnectInternal(false);
this.callbacks.onFail();
throw new Error("Failed to establish a connection!");
} finally {
this.duringExplicitConnectDisconnect--;
// Reset isReconnect for next time
this.isReconnect = false;
}
}

Expand Down Expand Up @@ -433,6 +450,7 @@ export const createBluetoothDeviceWrapper = async (
logging: Logging,
dispatchTypedEvent: TypedServiceEventDispatcher,
addedServiceListeners: Record<keyof ServiceConnectionEventMap, boolean>,
callbacks: ConnectCallbacks,
): Promise<BluetoothDeviceWrapper | undefined> => {
try {
// Reuse our connection objects for the same device as they
Expand All @@ -444,6 +462,7 @@ export const createBluetoothDeviceWrapper = async (
logging,
dispatchTypedEvent,
addedServiceListeners,
callbacks,
);
deviceIdToWrapper.set(device.id, bluetooth);
await bluetooth.connect();
Expand Down
13 changes: 12 additions & 1 deletion lib/bluetooth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export class MicrobitWebBluetoothConnection
});
} finally {
this.connection = undefined;
this.setStatus(ConnectionStatus.NOT_CONNECTED);
this.setStatus(ConnectionStatus.DISCONNECTED);
this.logging.log("Disconnection complete");
this.logging.event({
type: "Bluetooth-info",
Expand Down Expand Up @@ -154,14 +154,25 @@ export class MicrobitWebBluetoothConnection
if (!this.connection) {
const device = await this.chooseDevice();
if (!device) {
this.setStatus(ConnectionStatus.NO_AUTHORIZED_DEVICE);
return;
}
this.connection = await createBluetoothDeviceWrapper(
device,
this.logging,
this.dispatchTypedEvent.bind(this),
this.activeEvents,
{
onConnecting: () => this.setStatus(ConnectionStatus.CONNECTING),
onReconnecting: () => this.setStatus(ConnectionStatus.RECONNECTING),
onSuccess: () => this.setStatus(ConnectionStatus.CONNECTED),
onFail: () => {
this.setStatus(ConnectionStatus.DISCONNECTED);
this.connection = undefined;
},
},
);
return;
}
// TODO: timeout unification?
// Connection happens inside createBluetoothDeviceWrapper.
Expand Down
15 changes: 14 additions & 1 deletion lib/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,27 @@ export enum ConnectionStatus {
* but has not been connected via the browser security UI.
*/
NO_AUTHORIZED_DEVICE = "NO_AUTHORIZED_DEVICE",
/**
* Disconnecting.
*/
DISCONNECTING = "DISCONNECTING",
/**
* Authorized device available but we haven't connected to it.
*/
NOT_CONNECTED = "NOT_CONNECTED",
DISCONNECTED = "DISCONNECTED",
/**
* Connected.
*/
CONNECTED = "CONNECTED",
/**
* Connecting.
*/
CONNECTING = "CONNECTING",
/**
* Reconnecting. When there is unexpected disruption in the connection,
* a reconnection is attempted.
*/
RECONNECTING = "RECONNECTING",
}

export class FlashDataError extends Error {}
Expand Down
8 changes: 4 additions & 4 deletions lib/usb-radio-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ export class MicrobitRadioBridgeConnection
this.setStatus(e.status);
this.serialSession?.dispose();
} else {
this.status = ConnectionStatus.NOT_CONNECTED;
this.status = ConnectionStatus.DISCONNECTED;
if (
currentStatus === ConnectionStatus.NOT_CONNECTED &&
currentStatus === ConnectionStatus.DISCONNECTED &&
this.serialSessionOpen
) {
this.serialSession?.connect();
Expand Down Expand Up @@ -181,7 +181,7 @@ export class MicrobitRadioBridgeConnection

private statusFromDelegate(): ConnectionStatus {
return this.delegate.status == ConnectionStatus.CONNECTED
? ConnectionStatus.NOT_CONNECTED
? ConnectionStatus.DISCONNECTED
: this.delegate.status;
}
}
Expand Down Expand Up @@ -336,7 +336,7 @@ class RadioBridgeSerialSession {
this.delegate.removeEventListener("serialerror", this.serialErrorListener);
await this.delegate.softwareReset();

this.onStatusChanged(ConnectionStatus.NOT_CONNECTED);
this.onStatusChanged(ConnectionStatus.DISCONNECTED);
}

private async sendCmdWaitResponse(
Expand Down
4 changes: 2 additions & 2 deletions lib/usb.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ describeDeviceOnly("MicrobitWebUSBConnection (WebUSB supported)", () => {
await connection.disconnect();
connection.dispose();

expect(connection.status).toEqual(ConnectionStatus.NOT_CONNECTED);
expect(connection.status).toEqual(ConnectionStatus.DISCONNECTED);
expect(events).toEqual([
ConnectionStatus.CONNECTED,
ConnectionStatus.NOT_CONNECTED,
ConnectionStatus.DISCONNECTED,
]);
});
});
2 changes: 1 addition & 1 deletion lib/usb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ export class MicrobitWebUSBConnection
});
} finally {
this.connection = undefined;
this.setStatus(ConnectionStatus.NOT_CONNECTED);
this.setStatus(ConnectionStatus.DISCONNECTED);
this.logging.log("Disconnection complete");
this.logging.event({
type: "WebUSB-info",
Expand Down