Skip to content

Commit fd4fd4b

Browse files
committed
Fix BadgeUSB.sync(); add debug logging
1 parent 9732d3f commit fd4fd4b

File tree

2 files changed

+82
-21
lines changed

2 files changed

+82
-21
lines changed

src/badge-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export class BadgeAPI {
1818

1919
async connect() {
2020
this.badge = await BadgeUSB.connect();
21+
this.badge.onConnectionLost = () => delete this.badge;
2122
}
2223

2324
async disconnect(reset = false) {

src/badge-usb.ts

Lines changed: 81 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
import { crc32FromArrayBuffer } from "./lib/crc32";
77
import { concatBuffers } from "./lib/buffers";
88

9+
export enum BadgeUSBState {
10+
CDC = 0x00,
11+
WebUSB = 0x01,
12+
}
13+
914
export 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

Comments
 (0)