Skip to content

Commit 087cd8a

Browse files
Fix serial duplication in the Python Editor
Improve robustness of the start/stop of serial.
1 parent 4594d86 commit 087cd8a

File tree

2 files changed

+57
-56
lines changed

2 files changed

+57
-56
lines changed

lib/usb-device-wrapper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export class DAPWrapper {
120120
// Changing the baud rate causes a micro:bit reset, so only do it if necessary
121121
await this.daplink.setSerialBaudrate(115200);
122122
}
123-
this.daplink.on(DAPLink.EVENT_SERIAL_DATA, listener);
123+
this.daplink.addListener(DAPLink.EVENT_SERIAL_DATA, listener);
124124
await this.daplink.startSerialRead(1);
125125
}
126126

lib/usb.ts

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,27 @@
33
*
44
* SPDX-License-Identifier: MIT
55
*/
6-
import { Logging, NullLogging } from "./logging.js";
7-
import { withTimeout, TimeoutError } from "./async-util.js";
8-
import { DAPWrapper } from "./usb-device-wrapper.js";
9-
import { PartialFlashing } from "./usb-partial-flashing.js";
6+
import { TimeoutError, withTimeout } from "./async-util.js";
107
import {
8+
AfterRequestDevice,
9+
BeforeRequestDevice,
1110
BoardVersion,
1211
ConnectionStatus,
13-
ConnectOptions,
12+
ConnectionStatusEvent,
1413
DeviceConnection,
1514
DeviceConnectionEventMap,
16-
AfterRequestDevice,
15+
DeviceError,
16+
FlashDataError,
1717
FlashDataSource,
1818
FlashEvent,
19-
FlashDataError,
2019
SerialDataEvent,
2120
SerialErrorEvent,
2221
SerialResetEvent,
23-
BeforeRequestDevice,
24-
ConnectionStatusEvent,
25-
DeviceError,
2622
} from "./device.js";
2723
import { TypedEventTarget } from "./events.js";
28-
29-
interface InternalConnectOptions extends ConnectOptions {
30-
serial?: boolean;
31-
}
24+
import { Logging, NullLogging } from "./logging.js";
25+
import { DAPWrapper } from "./usb-device-wrapper.js";
26+
import { PartialFlashing } from "./usb-partial-flashing.js";
3227

3328
// Temporary workaround for ChromeOS 105 bug.
3429
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1363712&q=usb&can=2
@@ -66,11 +61,8 @@ export class MicrobitWebUSBConnection
6661
*/
6762
private connection: DAPWrapper | undefined;
6863

69-
/**
70-
* DAPLink gives us a promise that lasts as long as we're serial reading.
71-
* When stopping serial we await it to be sure we're done.
72-
*/
73-
private serialReadInProgress: Promise<void> | undefined;
64+
private serialState: boolean = false;
65+
private serialStateChange: Promise<void> | undefined;
7466

7567
private serialListener = (data: string) => {
7668
this.dispatchTypedEvent("serialdata", new SerialDataEvent(data));
@@ -139,22 +131,27 @@ export class MicrobitWebUSBConnection
139131
private _addEventListener = this.addEventListener;
140132
private _removeEventListener = this.removeEventListener;
141133

142-
private addedListeners = {
143-
serialdata: false,
134+
private addedListeners: Record<string, number> = {
135+
serialdata: 0,
144136
};
145137

146138
constructor(
147139
options: MicrobitWebUSBConnectionOptions = { logging: new NullLogging() },
148140
) {
149141
super();
150142
this.logging = options.logging;
143+
// TODO: this doesn't account for the rules around add/remove call equivalence
151144
this.addEventListener = (type, ...args) => {
152145
this._addEventListener(type, ...args);
153-
this.startNotifications(type);
146+
if (++this.addedListeners[type] === 1 && !this.flashing) {
147+
this.startNotifications(type);
148+
}
154149
};
155150
this.removeEventListener = (type, ...args) => {
156-
this.stopNotifications(type);
157151
this._removeEventListener(type, ...args);
152+
if (--this.addedListeners[type] === 0) {
153+
this.stopNotifications(type);
154+
}
158155
};
159156
}
160157

@@ -192,9 +189,9 @@ export class MicrobitWebUSBConnection
192189
}
193190
}
194191

195-
async connect(options: ConnectOptions = {}): Promise<ConnectionStatus> {
192+
async connect(): Promise<ConnectionStatus> {
196193
return this.withEnrichedErrors(async () => {
197-
await this.connectInternal(options);
194+
await this.connectInternal();
198195
return this.status;
199196
});
200197
}
@@ -241,9 +238,7 @@ export class MicrobitWebUSBConnection
241238
this.log("Stopping serial before flash");
242239
await this.stopSerialInternal();
243240
this.log("Reconnecting before flash");
244-
await this.connectInternal({
245-
serial: false,
246-
});
241+
await this.connectInternal();
247242
if (!this.connection) {
248243
throw new Error("Must be connected now");
249244
}
@@ -275,8 +270,6 @@ export class MicrobitWebUSBConnection
275270
await this.disconnect();
276271
this.visibilityReconnect = true;
277272
} else {
278-
// This might not strictly be "reinstating". We should make this
279-
// behaviour configurable when pulling out a library.
280273
if (this.addedListeners.serialdata) {
281274
this.log("Reinstating serial after flash");
282275
if (this.connection.daplink) {
@@ -289,30 +282,42 @@ export class MicrobitWebUSBConnection
289282
}
290283

291284
private async startSerialInternal() {
292-
if (!this.connection) {
293-
// As connecting then starting serial are async we could disconnect between them,
294-
// so handle this gracefully.
295-
return;
296-
}
297-
if (this.serialReadInProgress) {
298-
await this.stopSerialInternal();
299-
}
300-
// This is async but won't return until we stop serial so we error handle with an event.
301-
this.serialReadInProgress = this.connection
302-
.startSerial(this.serialListener)
303-
.then(() => this.log("Finished listening for serial data"))
304-
.catch((e) => {
305-
this.dispatchTypedEvent("serialerror", new SerialErrorEvent(e));
306-
});
285+
const prev = this.serialStateChange;
286+
this.serialStateChange = (async () => {
287+
await prev;
288+
289+
if (!this.connection || this.serialState) {
290+
return;
291+
}
292+
this.log("Starting serial");
293+
this.serialState = true;
294+
this.connection
295+
.startSerial(this.serialListener)
296+
.then(() => {
297+
this.log("Finished listening for serial data");
298+
})
299+
.catch((e) => {
300+
this.dispatchTypedEvent("serialerror", new SerialErrorEvent(e));
301+
})
302+
.finally(() => {
303+
this.serialState = false;
304+
});
305+
})();
306+
await this.serialStateChange;
307307
}
308308

309309
private async stopSerialInternal() {
310-
if (this.connection && this.serialReadInProgress) {
310+
const prev = this.serialStateChange;
311+
this.serialStateChange = (async () => {
312+
await prev;
313+
314+
if (!this.connection || !this.serialState) {
315+
return;
316+
}
311317
this.connection.stopSerial(this.serialListener);
312-
await this.serialReadInProgress;
313-
this.serialReadInProgress = undefined;
314318
this.dispatchTypedEvent("serialreset", new SerialResetEvent());
315-
}
319+
})();
320+
await this.serialStateChange;
316321
}
317322

318323
async disconnect(): Promise<void> {
@@ -410,15 +415,13 @@ export class MicrobitWebUSBConnection
410415
this.setStatus(ConnectionStatus.NO_AUTHORIZED_DEVICE);
411416
}
412417

413-
private async connectInternal(
414-
options: InternalConnectOptions,
415-
): Promise<void> {
418+
private async connectInternal(): Promise<void> {
416419
if (!this.connection) {
417420
const device = await this.chooseDevice();
418421
this.connection = new DAPWrapper(device, this.logging);
419422
}
420423
await withTimeout(this.connection.reconnectAsync(), 10_000);
421-
if (this.addedListeners.serialdata && options.serial !== false) {
424+
if (this.addedListeners.serialdata && !this.flashing) {
422425
this.startSerialInternal();
423426
}
424427
this.setStatus(ConnectionStatus.CONNECTED);
@@ -440,7 +443,6 @@ export class MicrobitWebUSBConnection
440443
switch (type as keyof DeviceConnectionEventMap) {
441444
case "serialdata": {
442445
this.startSerialInternal();
443-
this.addedListeners.serialdata = true;
444446
break;
445447
}
446448
}
@@ -450,7 +452,6 @@ export class MicrobitWebUSBConnection
450452
switch (type as keyof DeviceConnectionEventMap) {
451453
case "serialdata": {
452454
this.stopSerialInternal();
453-
this.addedListeners.serialdata = false;
454455
break;
455456
}
456457
}

0 commit comments

Comments
 (0)