From b509a04c681021db97e16b09313a675e291a1acc Mon Sep 17 00:00:00 2001 From: Grace Date: Tue, 30 Jul 2024 17:34:19 +0100 Subject: [PATCH 1/4] WIP --- lib/device.ts | 4 -- lib/usb-device-wrapper.ts | 1 + lib/usb-radio-bridge.ts | 87 +++++++++++++++++++++++++-------------- lib/usb.ts | 3 ++ 4 files changed, 59 insertions(+), 36 deletions(-) diff --git a/lib/device.ts b/lib/device.ts index c2b14a4..24b0c27 100644 --- a/lib/device.ts +++ b/lib/device.ts @@ -81,10 +81,6 @@ 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. */ diff --git a/lib/usb-device-wrapper.ts b/lib/usb-device-wrapper.ts index f3fb528..bf78db2 100644 --- a/lib/usb-device-wrapper.ts +++ b/lib/usb-device-wrapper.ts @@ -79,6 +79,7 @@ export class DAPWrapper { // Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L119 async reconnectAsync(): Promise { + console.log("reconnectAsync", this.initialConnectionComplete); if (this.initialConnectionComplete) { await this.disconnectAsync(); diff --git a/lib/usb-radio-bridge.ts b/lib/usb-radio-bridge.ts index e94e65a..bfefc05 100644 --- a/lib/usb-radio-bridge.ts +++ b/lib/usb-radio-bridge.ts @@ -32,6 +32,13 @@ export interface MicrobitRadioBridgeConnectionOptions { logging: Logging; } +interface ConnectCallbacks { + onConnecting: () => void; + onReconnecting: () => void; + onFail: () => void; + onSuccess: () => void; +} + /** * Wraps around a USB connection to implement a subset of services over a serial protocol. * @@ -48,7 +55,7 @@ export class MicrobitRadioBridgeConnection private disconnectPromise: Promise | undefined; private serialSessionOpen = false; - private delegateStatusListner = (e: ConnectionStatusEvent) => { + private delegateStatusListener = (e: ConnectionStatusEvent) => { const currentStatus = this.status; if (e.status !== ConnectionStatus.CONNECTED) { this.setStatus(e.status); @@ -84,11 +91,11 @@ export class MicrobitRadioBridgeConnection async initialize(): Promise { await this.delegate.initialize(); this.setStatus(this.statusFromDelegate()); - this.delegate.addEventListener("status", this.delegateStatusListner); + this.delegate.addEventListener("status", this.delegateStatusListener); } dispose(): void { - this.delegate.removeEventListener("status", this.delegateStatusListner); + this.delegate.removeEventListener("status", this.delegateStatusListener); this.delegate.dispose(); } @@ -124,24 +131,25 @@ export class MicrobitRadioBridgeConnection this.remoteDeviceId, this.delegate, this.dispatchTypedEvent.bind(this), - this.setStatus.bind(this), - () => { - // Remote connection lost - this.logging.event({ - type: "Serial", - message: "Serial connection lost 1", - }); - // This is the point we tell the consumer that we're trying to reconnect - // in the background. - // Leave serial connection running in case the remote device comes back. - }, - () => { - // Remote connection... even more lost? - this.logging.event({ - type: "Serial", - message: "Serial connection lost 2", - }); - this.serialSession?.dispose(); + { + onConnecting: () => this.setStatus(ConnectionStatus.CONNECTING), + onReconnecting: () => { + // Leave serial connection running in case the remote device comes back. + // Only set status as reconnecting once + if (this.status !== ConnectionStatus.RECONNECTING) { + this.setStatus(ConnectionStatus.RECONNECTING); + } + }, + onFail: () => { + this.setStatus(ConnectionStatus.DISCONNECTED); + this.serialSession?.dispose(); + this.serialSessionOpen = false; + }, + onSuccess: () => { + if (this.status !== ConnectionStatus.CONNECTED) { + this.setStatus(ConnectionStatus.CONNECTED); + } + }, }, ); @@ -174,8 +182,13 @@ export class MicrobitRadioBridgeConnection })(); } + private log(v: any) { + this.logging.log(v); + } + private setStatus(status: ConnectionStatus) { this.status = status; + this.log("Radio connection status " + status); this.dispatchTypedEvent("status", new ConnectionStatusEvent(status)); } @@ -267,18 +280,15 @@ class RadioBridgeSerialSession { private remoteDeviceId: number, private delegate: MicrobitWebUSBConnection, private dispatchTypedEvent: TypedServiceEventDispatcher, - private onStatusChanged: (status: ConnectionStatus) => void, - private onRemoteConnectionLost1: () => void, - private onRemoteConnectionLost2: () => void, + private callbacks: ConnectCallbacks, ) {} async connect() { this.delegate.addEventListener("serialdata", this.serialDataListener); this.delegate.addEventListener("serialerror", this.serialErrorListener); - try { + this.callbacks.onConnecting(); await this.handshake(); - this.onStatusChanged(ConnectionStatus.CONNECTED); this.logging.log(`Serial: using remote device id ${this.remoteDeviceId}`); const remoteMbIdCommand = protocol.generateCmdRemoteMbId( @@ -319,8 +329,9 @@ class RadioBridgeSerialSession { await periodicMessagePromise; this.startConnectionCheck(); + this.callbacks.onSuccess(); } catch (e) { - this.dispose(); + this.callbacks.onFail(); } } @@ -335,8 +346,6 @@ class RadioBridgeSerialSession { this.delegate.removeEventListener("serialdata", this.serialDataListener); this.delegate.removeEventListener("serialerror", this.serialErrorListener); await this.delegate.softwareReset(); - - this.onStatusChanged(ConnectionStatus.DISCONNECTED); } private async sendCmdWaitResponse( @@ -358,19 +367,33 @@ class RadioBridgeSerialSession { private startConnectionCheck() { // Check for connection lost if (this.connectionCheckIntervalId === undefined) { - this.connectionCheckIntervalId = setInterval(async () => { + this.connectionCheckIntervalId = setInterval(() => { + if ( + this.lastReceivedMessageTimestamp && + Date.now() - this.lastReceivedMessageTimestamp <= 1_000 + ) { + this.callbacks.onSuccess(); + } if ( this.lastReceivedMessageTimestamp && Date.now() - this.lastReceivedMessageTimestamp > 1_000 ) { - this.onRemoteConnectionLost1(); + this.logging.event({ + type: "Serial", + message: "Serial connection lost - attempting to reconnect", + }); + this.callbacks.onReconnecting(); } if ( this.lastReceivedMessageTimestamp && Date.now() - this.lastReceivedMessageTimestamp > connectTimeoutDuration ) { - this.onRemoteConnectionLost2(); + this.logging.event({ + type: "Serial", + message: "Serial connection lost", + }); + this.callbacks.onFail(); } }, 1000); } diff --git a/lib/usb.ts b/lib/usb.ts index fc7ba18..8d9209f 100644 --- a/lib/usb.ts +++ b/lib/usb.ts @@ -419,12 +419,15 @@ export class MicrobitWebUSBConnection } private async connectInternal(): Promise { + console.log("connect internal"); if (!this.connection) { const device = await this.chooseDevice(); this.connection = new DAPWrapper(device, this.logging); } await withTimeout(this.connection.reconnectAsync(), 10_000); if (this.addedListeners.serialdata && !this.flashing) { + console.log("start serial internal"); + this.startSerialInternal(); } this.setStatus(ConnectionStatus.CONNECTED); From 5dc9c485275015ca11e250404afe33c1bc426140 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 1 Aug 2024 11:47:58 +0100 Subject: [PATCH 2/4] Only set serialSessionOpen when radio connection is successful --- lib/usb-radio-bridge.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/usb-radio-bridge.ts b/lib/usb-radio-bridge.ts index bfefc05..ae3138e 100644 --- a/lib/usb-radio-bridge.ts +++ b/lib/usb-radio-bridge.ts @@ -149,12 +149,12 @@ export class MicrobitRadioBridgeConnection if (this.status !== ConnectionStatus.CONNECTED) { this.setStatus(ConnectionStatus.CONNECTED); } + this.serialSessionOpen = true; }, }, ); await this.serialSession.connect(); - this.serialSessionOpen = true; this.logging.event({ type: "Connect", @@ -162,6 +162,7 @@ export class MicrobitRadioBridgeConnection }); return this.status; } catch (e) { + this.serialSessionOpen = false; this.logging.error("Failed to initialise serial protocol", e); this.logging.event({ type: "Connect", From faa2f10f83bab327f3f62fdbf1b640727901c2c5 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 1 Aug 2024 11:51:52 +0100 Subject: [PATCH 3/4] Remove debugging logs --- lib/usb-device-wrapper.ts | 1 - lib/usb.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/lib/usb-device-wrapper.ts b/lib/usb-device-wrapper.ts index bf78db2..f3fb528 100644 --- a/lib/usb-device-wrapper.ts +++ b/lib/usb-device-wrapper.ts @@ -79,7 +79,6 @@ export class DAPWrapper { // Drawn from https://github.com/microsoft/pxt-microbit/blob/dec5b8ce72d5c2b4b0b20aafefce7474a6f0c7b2/editor/extension.tsx#L119 async reconnectAsync(): Promise { - console.log("reconnectAsync", this.initialConnectionComplete); if (this.initialConnectionComplete) { await this.disconnectAsync(); diff --git a/lib/usb.ts b/lib/usb.ts index 8d9209f..fc7ba18 100644 --- a/lib/usb.ts +++ b/lib/usb.ts @@ -419,15 +419,12 @@ export class MicrobitWebUSBConnection } private async connectInternal(): Promise { - console.log("connect internal"); if (!this.connection) { const device = await this.chooseDevice(); this.connection = new DAPWrapper(device, this.logging); } await withTimeout(this.connection.reconnectAsync(), 10_000); if (this.addedListeners.serialdata && !this.flashing) { - console.log("start serial internal"); - this.startSerialInternal(); } this.setStatus(ConnectionStatus.CONNECTED); From e842476ae5d2b2a663b9452eca9c6277fd2203f8 Mon Sep 17 00:00:00 2001 From: Grace Date: Thu, 1 Aug 2024 11:58:03 +0100 Subject: [PATCH 4/4] Ensure all status are only set once --- lib/usb-radio-bridge.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/usb-radio-bridge.ts b/lib/usb-radio-bridge.ts index ae3138e..15fa849 100644 --- a/lib/usb-radio-bridge.ts +++ b/lib/usb-radio-bridge.ts @@ -135,13 +135,14 @@ export class MicrobitRadioBridgeConnection onConnecting: () => this.setStatus(ConnectionStatus.CONNECTING), onReconnecting: () => { // Leave serial connection running in case the remote device comes back. - // Only set status as reconnecting once if (this.status !== ConnectionStatus.RECONNECTING) { this.setStatus(ConnectionStatus.RECONNECTING); } }, onFail: () => { - this.setStatus(ConnectionStatus.DISCONNECTED); + if (this.status !== ConnectionStatus.DISCONNECTED) { + this.setStatus(ConnectionStatus.DISCONNECTED); + } this.serialSession?.dispose(); this.serialSessionOpen = false; },