66import { crc32FromArrayBuffer } from "./lib/crc32" ;
77import { concatBuffers } from "./lib/buffers" ;
88
9+ export enum BadgeUSBState {
10+ CDC = 0x00 ,
11+ WebUSB = 0x01 ,
12+ }
13+
914export class BadgeUSB {
1015 static filters : USBDeviceFilter [ ] = [
1116 { vendorId : 0x16d0 , productId : 0x0f9a } // MCH2022 badge
@@ -80,9 +85,11 @@ export class BadgeUSB {
8085 throw new Error ( "Browser does not support WebUSB" ) ;
8186 }
8287
88+ console . debug ( 'Requesting device from user agent...' ) ;
8389 const usbDevice = await navigator . usb . requestDevice ( {
8490 filters : this . filters
8591 } ) ;
92+ console . log ( 'Selected device:' , usbDevice ) ;
8693
8794 await usbDevice . open ( ) ;
8895 await usbDevice . selectConfiguration ( this . defaultConfiguration ) ;
@@ -100,7 +107,8 @@ export class BadgeUSB {
100107
101108 const badge = new BadgeUSB ( usbDevice , interfaceIndex , endpoints ) ;
102109
103- await badge . controlSetState ( true ) ;
110+ console . debug ( 'Connecting: requesting device to enter WebUSB mode...' ) ;
111+ await badge . controlSetState ( BadgeUSBState . WebUSB ) ;
104112 await badge . controlSetBaudrate ( 921600 ) ;
105113
106114 let currentMode = await badge . controlGetMode ( ) ;
@@ -110,17 +118,31 @@ export class BadgeUSB {
110118 }
111119
112120 badge . _listen ( ) ;
121+ console . debug ( 'Connecting: started listening for incoming data' ) ;
122+
123+ console . time ( 'Connecting: bus synchronized' ) ;
113124
114125 let protocolVersion : number | undefined ;
115- while ( protocolVersion == undefined ) {
116- await badge . sync ( ) . then ( v => protocolVersion = v ) . catch ( ) ;
117- }
126+ let n = 0 ;
127+ do {
128+ if ( ++ n > 100 ) {
129+ throw new Error ( `Sync failed after ${ n } tries` ) ;
130+ }
131+ console . debug ( 'Connecting: syncing bus: attempt' , n ) ;
132+ await badge . sync ( ) . then ( v => protocolVersion = v ) . catch ( ( ) => { } ) ;
133+
134+ } while ( protocolVersion == undefined )
135+
136+ console . timeEnd ( 'Connecting: bus synchronized' ) ;
137+ console . debug ( `Connecting: bus synchronized in ${ n } attempts` ) ;
138+ console . debug ( `Protocol version: ${ protocolVersion } ` ) ;
118139
119140 if ( protocolVersion < 2 ) {
120141 throw new Error ( "Protocol version not supported" ) ;
121142 }
122143
123144 badge . connected = true ;
145+ console . log ( 'Connected to badge! 🎉' ) ;
124146
125147 return badge ;
126148 }
@@ -139,19 +161,26 @@ export class BadgeUSB {
139161 this . connected = false ;
140162 try {
141163 this . _stopListening ( ) ;
142- await this . controlSetMode ( BadgeUSB . MODE_NORMAL ) ;
143- if ( reset ) await this . controlReset ( false ) ;
144- await this . controlSetState ( false ) ;
164+
165+ if ( reset ) {
166+ console . debug ( 'Disconnecting: requesting device to reset...' ) ;
167+ await this . controlReset ( false ) ;
168+ } else {
169+ console . debug ( 'Disconnecting: requesting device to exit WebUSB mode...' ) ;
170+ await this . controlSetMode ( BadgeUSB . MODE_NORMAL ) ;
171+ }
172+
173+ console . debug ( 'Disconnecting: resetting and releasing device USB interface...' ) ;
174+ await this . controlSetState ( BadgeUSBState . CDC ) ;
145175 await this . device . releaseInterface ( this . interfaceIndex ) ;
146176 } catch ( error ) {
147177 // Ignore errors
148178 }
149179 await this . device . close ( ) ;
150- this . nextTransactionID = 0 ;
180+ console . log ( 'Disconnecting: done' ) ;
181+ console . log ( 'Session stats:' , this . connectionStats ) ;
151182
152- if ( this . _onDisconnect ) {
153- this . _onDisconnect ( ) ;
154- }
183+ if ( this . _onDisconnect ) this . _onDisconnect ( ) ;
155184 }
156185
157186 set onConnectionLost ( callback : ( ) => void ) {
@@ -162,6 +191,16 @@ export class BadgeUSB {
162191 this . _onDisconnect = callback ;
163192 }
164193
194+ get connectionStats ( ) {
195+ return {
196+ rxPackets : this . rxPacketCount ,
197+ txPackets : this . txPacketCount ,
198+ timesOutOfSync : this . resyncCount ,
199+ transactions : this . nextTransactionID ,
200+ pendingTransactions : Object . keys ( this . transactionPromises ) . length ,
201+ } ;
202+ }
203+
165204 get manufacturerName ( ) {
166205 this . assertConnected ( ) ;
167206 return this . device . manufacturerName ;
@@ -177,8 +216,11 @@ export class BadgeUSB {
177216 return this . device . serialNumber ;
178217 }
179218
180- async controlSetState ( state : boolean ) {
181- await this . _controlTransferOut ( BadgeUSB . REQUEST_STATE , state ? 0x0001 : 0x0000 ) ;
219+ async controlSetState ( state : BadgeUSBState ) {
220+ await this . _controlTransferOut (
221+ BadgeUSB . REQUEST_STATE ,
222+ state == BadgeUSBState . WebUSB ? BadgeUSBState . WebUSB : BadgeUSBState . CDC ,
223+ ) ;
182224 }
183225
184226 async controlReset ( bootloaderMode = false ) {
@@ -207,17 +249,24 @@ export class BadgeUSB {
207249 * @returns the protocol version number
208250 * @throws an error if sync fails
209251 **/
210- async sync ( ) : Promise < number > {
252+ async sync ( ) : Promise < number | undefined > {
211253 this . dataBuffer = new ArrayBuffer ( 0 ) ; // reset buffer
212254
213- let result = await this . transaction ( BadgeUSB . PROTOCOL_COMMAND_SYNC , new ArrayBuffer ( 0 ) , 100 ) ;
255+ let result = await this . transaction ( BadgeUSB . PROTOCOL_COMMAND_SYNC , new ArrayBuffer ( 0 ) , 100 )
256+ . catch ( e => { if ( e ?. message != 'timeout' ) throw e } ) ;
257+
258+ if ( result === undefined ) return ;
259+
214260 this . inSync = true ;
215261 return new DataView ( result ) . getUint16 ( 0 , true ) ;
216262 }
217263
264+ private resyncCount = 0 ;
218265 async syncIfNeeded ( ) : Promise < void > {
266+ if ( ! this . inSync ) this . resyncCount ++ ;
267+
219268 while ( ! this . inSync ) {
220- await this . sync ( ) . catch ( ) ;
269+ await this . sync ( ) . catch ( ( ) => { } ) ;
221270 }
222271 }
223272
@@ -285,11 +334,11 @@ export class BadgeUSB {
285334 await this . _handleData ( result . buffer ) ;
286335 }
287336 } catch ( error ) {
288- console . error ( error ) ;
337+ console . error ( 'FATAL Error while listening for data:' , error ) ;
338+ console . warn ( 'Connection lost. If this was not intentional, try reconnecting.' ) ;
339+
289340 this . listening = false ;
290- if ( this . _onConnectionLost ) {
291- this . _onConnectionLost ( ) ;
292- }
341+ if ( this . _onConnectionLost ) this . _onConnectionLost ( ) ;
293342 }
294343 }
295344
@@ -336,6 +385,7 @@ export class BadgeUSB {
336385 return result . data ;
337386 }
338387
388+ private txPacketCount = 0 ;
339389 private async _sendPacket ( identifier : number , command : number , payload : ArrayBuffer | null = null ) {
340390 if ( payload === null ) payload = new ArrayBuffer ( 0 ) ;
341391
@@ -346,8 +396,11 @@ export class BadgeUSB {
346396 dataView . setUint32 ( 8 , command , true ) ;
347397 dataView . setUint32 ( 12 , payload . byteLength , true ) ;
348398 dataView . setUint32 ( 16 , payload . byteLength > 0 ? crc32FromArrayBuffer ( payload ) : 0 , true ) ;
399+
349400 let packet = concatBuffers ( [ header , payload ] ) ;
350401 await this . _dataTransferOut ( packet ) ;
402+
403+ this . txPacketCount ++ ;
351404 }
352405
353406 private async _handleData ( buffer : ArrayBuffer ) {
@@ -370,6 +423,8 @@ export class BadgeUSB {
370423 }
371424 }
372425
426+ private rxPacketCount = 0 ;
427+ private crcMismatchCount = 0 ;
373428 private async _handlePacket ( buffer : ArrayBuffer ) {
374429 let dataView = new DataView ( buffer ) ;
375430 let magic = dataView . getUint32 ( 0 , true ) ;
@@ -382,6 +437,8 @@ export class BadgeUSB {
382437 if ( payloadLength > 0 ) {
383438 payload = buffer . slice ( 20 ) ;
384439 if ( crc32FromArrayBuffer ( payload ) !== payloadCRC ) {
440+ console . debug ( 'CRC mismatch; mismatches so far:' , ++ this . crcMismatchCount ) ;
441+
385442 if ( identifier in this . transactionPromises ) {
386443 if ( this . transactionPromises [ identifier ] . timeout !== null ) {
387444 clearTimeout ( this . transactionPromises [ identifier ] . timeout ) ;
@@ -421,6 +478,7 @@ export class BadgeUSB {
421478 responseText : BadgeUSB . textDecoder . decode ( new Uint8Array ( buffer . slice ( 8 , 12 ) ) ) ,
422479 } ) ;
423480 delete this . transactionPromises [ identifier ] ;
481+ this . rxPacketCount ++ ;
424482 } else {
425483 console . error ( "Found no transaction for" , identifier , responseType ) ;
426484 }
@@ -433,14 +491,16 @@ class TransactionPromise extends Promise<TransactionResponse> {
433491
434492 timeout ?: number ;
435493
436- constructor ( ) {
494+ constructor ( executor : ConstructorParameters < typeof Promise < TransactionResponse > > [ 0 ] = ( ) => { } ) {
437495 let resolver : ( value : TransactionResponse | PromiseLike < TransactionResponse > ) => void ;
438496 let rejector : ( reason : TransactionResponse | Error ) => void ;
439497
440498 super ( ( resolve , reject ) => {
441499 resolver = resolve ;
442500 rejector = reject ;
501+ return executor ( resolve , reject ) ; // Promise magic: this line is essential but idk why
443502 } ) ;
503+
444504 this . resolve = resolver ! ;
445505 this . reject = rejector ! ;
446506 }
0 commit comments