3
3
*
4
4
* SPDX-License-Identifier: MIT
5
5
*/
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" ;
10
7
import {
8
+ AfterRequestDevice ,
9
+ BeforeRequestDevice ,
11
10
BoardVersion ,
12
11
ConnectionStatus ,
13
- ConnectOptions ,
12
+ ConnectionStatusEvent ,
14
13
DeviceConnection ,
15
14
DeviceConnectionEventMap ,
16
- AfterRequestDevice ,
15
+ DeviceError ,
16
+ FlashDataError ,
17
17
FlashDataSource ,
18
18
FlashEvent ,
19
- FlashDataError ,
20
19
SerialDataEvent ,
21
20
SerialErrorEvent ,
22
21
SerialResetEvent ,
23
- BeforeRequestDevice ,
24
- ConnectionStatusEvent ,
25
- DeviceError ,
26
22
} from "./device.js" ;
27
23
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" ;
32
27
33
28
// Temporary workaround for ChromeOS 105 bug.
34
29
// See https://bugs.chromium.org/p/chromium/issues/detail?id=1363712&q=usb&can=2
@@ -66,11 +61,8 @@ export class MicrobitWebUSBConnection
66
61
*/
67
62
private connection : DAPWrapper | undefined ;
68
63
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 ;
74
66
75
67
private serialListener = ( data : string ) => {
76
68
this . dispatchTypedEvent ( "serialdata" , new SerialDataEvent ( data ) ) ;
@@ -139,22 +131,27 @@ export class MicrobitWebUSBConnection
139
131
private _addEventListener = this . addEventListener ;
140
132
private _removeEventListener = this . removeEventListener ;
141
133
142
- private addedListeners = {
143
- serialdata : false ,
134
+ private addedListeners : Record < string , number > = {
135
+ serialdata : 0 ,
144
136
} ;
145
137
146
138
constructor (
147
139
options : MicrobitWebUSBConnectionOptions = { logging : new NullLogging ( ) } ,
148
140
) {
149
141
super ( ) ;
150
142
this . logging = options . logging ;
143
+ // TODO: this doesn't account for the rules around add/remove call equivalence
151
144
this . addEventListener = ( type , ...args ) => {
152
145
this . _addEventListener ( type , ...args ) ;
153
- this . startNotifications ( type ) ;
146
+ if ( ++ this . addedListeners [ type ] === 1 && ! this . flashing ) {
147
+ this . startNotifications ( type ) ;
148
+ }
154
149
} ;
155
150
this . removeEventListener = ( type , ...args ) => {
156
- this . stopNotifications ( type ) ;
157
151
this . _removeEventListener ( type , ...args ) ;
152
+ if ( -- this . addedListeners [ type ] === 0 ) {
153
+ this . stopNotifications ( type ) ;
154
+ }
158
155
} ;
159
156
}
160
157
@@ -192,9 +189,9 @@ export class MicrobitWebUSBConnection
192
189
}
193
190
}
194
191
195
- async connect ( options : ConnectOptions = { } ) : Promise < ConnectionStatus > {
192
+ async connect ( ) : Promise < ConnectionStatus > {
196
193
return this . withEnrichedErrors ( async ( ) => {
197
- await this . connectInternal ( options ) ;
194
+ await this . connectInternal ( ) ;
198
195
return this . status ;
199
196
} ) ;
200
197
}
@@ -241,9 +238,7 @@ export class MicrobitWebUSBConnection
241
238
this . log ( "Stopping serial before flash" ) ;
242
239
await this . stopSerialInternal ( ) ;
243
240
this . log ( "Reconnecting before flash" ) ;
244
- await this . connectInternal ( {
245
- serial : false ,
246
- } ) ;
241
+ await this . connectInternal ( ) ;
247
242
if ( ! this . connection ) {
248
243
throw new Error ( "Must be connected now" ) ;
249
244
}
@@ -275,8 +270,6 @@ export class MicrobitWebUSBConnection
275
270
await this . disconnect ( ) ;
276
271
this . visibilityReconnect = true ;
277
272
} else {
278
- // This might not strictly be "reinstating". We should make this
279
- // behaviour configurable when pulling out a library.
280
273
if ( this . addedListeners . serialdata ) {
281
274
this . log ( "Reinstating serial after flash" ) ;
282
275
if ( this . connection . daplink ) {
@@ -289,30 +282,42 @@ export class MicrobitWebUSBConnection
289
282
}
290
283
291
284
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 ;
307
307
}
308
308
309
309
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
+ }
311
317
this . connection . stopSerial ( this . serialListener ) ;
312
- await this . serialReadInProgress ;
313
- this . serialReadInProgress = undefined ;
314
318
this . dispatchTypedEvent ( "serialreset" , new SerialResetEvent ( ) ) ;
315
- }
319
+ } ) ( ) ;
320
+ await this . serialStateChange ;
316
321
}
317
322
318
323
async disconnect ( ) : Promise < void > {
@@ -410,15 +415,13 @@ export class MicrobitWebUSBConnection
410
415
this . setStatus ( ConnectionStatus . NO_AUTHORIZED_DEVICE ) ;
411
416
}
412
417
413
- private async connectInternal (
414
- options : InternalConnectOptions ,
415
- ) : Promise < void > {
418
+ private async connectInternal ( ) : Promise < void > {
416
419
if ( ! this . connection ) {
417
420
const device = await this . chooseDevice ( ) ;
418
421
this . connection = new DAPWrapper ( device , this . logging ) ;
419
422
}
420
423
await withTimeout ( this . connection . reconnectAsync ( ) , 10_000 ) ;
421
- if ( this . addedListeners . serialdata && options . serial !== false ) {
424
+ if ( this . addedListeners . serialdata && ! this . flashing ) {
422
425
this . startSerialInternal ( ) ;
423
426
}
424
427
this . setStatus ( ConnectionStatus . CONNECTED ) ;
@@ -440,7 +443,6 @@ export class MicrobitWebUSBConnection
440
443
switch ( type as keyof DeviceConnectionEventMap ) {
441
444
case "serialdata" : {
442
445
this . startSerialInternal ( ) ;
443
- this . addedListeners . serialdata = true ;
444
446
break ;
445
447
}
446
448
}
@@ -450,7 +452,6 @@ export class MicrobitWebUSBConnection
450
452
switch ( type as keyof DeviceConnectionEventMap ) {
451
453
case "serialdata" : {
452
454
this . stopSerialInternal ( ) ;
453
- this . addedListeners . serialdata = false ;
454
455
break ;
455
456
}
456
457
}
0 commit comments