Skip to content

Commit 09cbcad

Browse files
Restart connection if radio bridge reconnect timeouts (#25)
Also, ensure delegate is disconnected before software reset to prevent dapjs error (Error: Bad status for 8 -> 255)
1 parent 7090fe0 commit 09cbcad

File tree

2 files changed

+47
-16
lines changed

2 files changed

+47
-16
lines changed

lib/usb-radio-bridge.ts

+44-15
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export interface MicrobitRadioBridgeConnectionOptions {
3535
interface ConnectCallbacks {
3636
onConnecting: () => void;
3737
onReconnecting: () => void;
38+
onRestartConnection: () => void;
3839
onFail: () => void;
3940
onSuccess: () => void;
4041
}
@@ -54,8 +55,12 @@ export class MicrobitRadioBridgeConnection
5455
private remoteDeviceId: number | undefined;
5556
private disconnectPromise: Promise<void> | undefined;
5657
private serialSessionOpen = false;
58+
private ignoreDelegateStatus = false;
5759

5860
private delegateStatusListener = (e: ConnectionStatusEvent) => {
61+
if (this.ignoreDelegateStatus) {
62+
return;
63+
}
5964
const currentStatus = this.status;
6065
if (e.status !== ConnectionStatus.CONNECTED) {
6166
this.setStatus(e.status);
@@ -122,7 +127,7 @@ export class MicrobitRadioBridgeConnection
122127
type: "Connect",
123128
message: "Serial connect start",
124129
});
125-
130+
this.ignoreDelegateStatus = false;
126131
await this.delegate.connect();
127132

128133
try {
@@ -139,17 +144,23 @@ export class MicrobitRadioBridgeConnection
139144
this.setStatus(ConnectionStatus.RECONNECTING);
140145
}
141146
},
147+
onRestartConnection: () => {
148+
// So that serial session does not get repetitively disposed in
149+
// delegate status listener when delegate is disconnected for restarting connection
150+
this.ignoreDelegateStatus = true;
151+
},
142152
onFail: () => {
143153
if (this.status !== ConnectionStatus.DISCONNECTED) {
144154
this.setStatus(ConnectionStatus.DISCONNECTED);
145155
}
146-
this.serialSession?.dispose();
156+
this.ignoreDelegateStatus = false;
147157
this.serialSessionOpen = false;
148158
},
149159
onSuccess: () => {
150160
if (this.status !== ConnectionStatus.CONNECTED) {
151161
this.setStatus(ConnectionStatus.CONNECTED);
152162
}
163+
this.ignoreDelegateStatus = false;
153164
this.serialSessionOpen = true;
154165
},
155166
},
@@ -177,9 +188,9 @@ export class MicrobitRadioBridgeConnection
177188
if (this.disconnectPromise) {
178189
return this.disconnectPromise;
179190
}
191+
this.serialSessionOpen = false;
180192
this.disconnectPromise = (async () => {
181-
this.serialSessionOpen = false;
182-
await this.serialSession?.dispose();
193+
await this.serialSession?.dispose(true);
183194
this.disconnectPromise = undefined;
184195
})();
185196
}
@@ -210,6 +221,7 @@ class RadioBridgeSerialSession {
210221
private onPeriodicMessageReceived: (() => void) | undefined;
211222
private lastReceivedMessageTimestamp: number | undefined;
212223
private connectionCheckIntervalId: ReturnType<typeof setInterval> | undefined;
224+
private isRestartingConnection: boolean = false;
213225

214226
private serialErrorListener = (e: unknown) => {
215227
this.logging.error("Serial error", e);
@@ -289,7 +301,11 @@ class RadioBridgeSerialSession {
289301
this.delegate.addEventListener("serialdata", this.serialDataListener);
290302
this.delegate.addEventListener("serialerror", this.serialErrorListener);
291303
try {
292-
this.callbacks.onConnecting();
304+
if (this.isRestartingConnection) {
305+
this.callbacks.onReconnecting();
306+
} else {
307+
this.callbacks.onConnecting();
308+
}
293309
await this.handshake();
294310

295311
this.logging.log(`Serial: using remote device id ${this.remoteDeviceId}`);
@@ -330,14 +346,16 @@ class RadioBridgeSerialSession {
330346
// TODO: in the first-time connection case we used to move the error/disconnect to the background here, why? timing?
331347
await periodicMessagePromise;
332348

333-
this.startConnectionCheck();
349+
this.isRestartingConnection = false;
350+
await this.startConnectionCheck();
334351
this.callbacks.onSuccess();
335352
} catch (e) {
336353
this.callbacks.onFail();
354+
await this.dispose();
337355
}
338356
}
339357

340-
async dispose() {
358+
async dispose(disconnect: boolean = false) {
341359
this.stopConnectionCheck();
342360
try {
343361
await this.sendCmdWaitResponse(protocol.generateCmdStop());
@@ -347,6 +365,9 @@ class RadioBridgeSerialSession {
347365
this.responseMap.clear();
348366
this.delegate.removeEventListener("serialdata", this.serialDataListener);
349367
this.delegate.removeEventListener("serialerror", this.serialErrorListener);
368+
if (disconnect) {
369+
await this.delegate.disconnect();
370+
}
350371
await this.delegate.softwareReset();
351372
}
352373

@@ -366,10 +387,10 @@ class RadioBridgeSerialSession {
366387
return responsePromise;
367388
}
368389

369-
private startConnectionCheck() {
390+
private async startConnectionCheck() {
370391
// Check for connection lost
371392
if (this.connectionCheckIntervalId === undefined) {
372-
this.connectionCheckIntervalId = setInterval(() => {
393+
this.connectionCheckIntervalId = setInterval(async () => {
373394
if (
374395
this.lastReceivedMessageTimestamp &&
375396
Date.now() - this.lastReceivedMessageTimestamp <= 1_000
@@ -382,7 +403,7 @@ class RadioBridgeSerialSession {
382403
) {
383404
this.logging.event({
384405
type: "Serial",
385-
message: "Serial connection lost - attempting to reconnect",
406+
message: "Serial connection lost...attempt to reconnect",
386407
});
387408
this.callbacks.onReconnecting();
388409
}
@@ -391,16 +412,24 @@ class RadioBridgeSerialSession {
391412
Date.now() - this.lastReceivedMessageTimestamp >
392413
connectTimeoutDuration
393414
) {
394-
this.logging.event({
395-
type: "Serial",
396-
message: "Serial connection lost",
397-
});
398-
this.callbacks.onFail();
415+
await this.restartConnection();
399416
}
400417
}, 1000);
401418
}
402419
}
403420

421+
private async restartConnection() {
422+
this.isRestartingConnection = true;
423+
this.logging.event({
424+
type: "Serial",
425+
message: "Serial connection lost...restart connection",
426+
});
427+
this.callbacks.onRestartConnection();
428+
await this.dispose(true);
429+
await this.delegate.connect();
430+
await this.connect();
431+
}
432+
404433
private stopConnectionCheck() {
405434
clearInterval(this.connectionCheckIntervalId);
406435
this.connectionCheckIntervalId = undefined;

lib/usb.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,9 @@ export class MicrobitWebUSBConnection
384384
}
385385

386386
async softwareReset(): Promise<void> {
387-
await this.connection?.softwareReset();
387+
return this.serialStateChangeQueue.add(
388+
async () => await this.connection?.softwareReset(),
389+
);
388390
}
389391

390392
private handleDisconnect = (event: USBConnectionEvent) => {

0 commit comments

Comments
 (0)