Skip to content

Commit 5bbb95c

Browse files
committed
Fix BT reboot and CRC issues
1 parent 501b95b commit 5bbb95c

File tree

6 files changed

+301
-128
lines changed

6 files changed

+301
-128
lines changed

src/js/WebBluetooth.js

Whitespace-only changes.

src/js/protocols/WebBluetooth.js

+123-88
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ class WebBluetooth extends EventTarget {
4646
this.bluetooth.addEventListener("gatserverdisconnected", (e) => this.handleRemovedDevice(e.target));
4747

4848
this.loadDevices();
49+
50+
// Properly bind all event handlers ONCE
51+
this.boundHandleDisconnect = this.handleDisconnect.bind(this);
52+
this.boundHandleNotification = this.handleNotification.bind(this);
53+
this.boundHandleReceiveBytes = this.handleReceiveBytes.bind(this);
4954
}
5055

5156
handleNewDevice(device) {
@@ -135,26 +140,26 @@ class WebBluetooth extends EventTarget {
135140

136141
console.log(`${this.logHead} Opening connection with ID: ${path}, Baud: ${options.baudRate}`);
137142

138-
this.device.addEventListener("gattserverdisconnected", this.handleDisconnect.bind(this));
143+
// Use bound method references
144+
this.device.addEventListener("gattserverdisconnected", this.boundHandleDisconnect);
139145

140146
try {
141147
console.log(`${this.logHead} Connecting to GATT Server`);
142148

143149
await this.gattConnect();
144150

151+
// Check if the GATT connection was successful before proceeding
152+
if (!this.device.gatt?.connected) {
153+
throw new Error("GATT server connection failed");
154+
}
155+
145156
gui_log(i18n.getMessage("bluetoothConnected", [this.device.name]));
146157

147158
await this.getServices();
148159
await this.getCharacteristics();
149160
await this.startNotifications();
150-
} catch (error) {
151-
gui_log(i18n.getMessage("bluetoothConnectionError", [error]));
152-
}
153-
154-
// Bluetooth API doesn't provide a way for getInfo() or similar to get the connection info
155-
const connectionInfo = this.device.gatt.connected;
156161

157-
if (connectionInfo && !this.openCanceled) {
162+
// Connection is fully established only after all setup completes successfully
158163
this.connected = true;
159164
this.connectionId = this.device.port;
160165
this.bitrate = options.baudRate;
@@ -163,35 +168,25 @@ class WebBluetooth extends EventTarget {
163168
this.failed = 0;
164169
this.openRequested = false;
165170

166-
this.device.addEventListener("disconnect", this.handleDisconnect.bind(this));
167-
this.addEventListener("receive", this.handleReceiveBytes);
171+
// Use bound references here too
172+
this.device.addEventListener("disconnect", this.boundHandleDisconnect);
173+
this.addEventListener("receive", this.boundHandleReceiveBytes);
168174

169175
console.log(`${this.logHead} Connection opened with ID: ${this.connectionId}, Baud: ${options.baudRate}`);
170176

171-
this.dispatchEvent(new CustomEvent("connect", { detail: connectionInfo }));
172-
} else if (connectionInfo && this.openCanceled) {
173-
this.connectionId = this.device.port;
177+
this.dispatchEvent(new CustomEvent("connect", { detail: true }));
178+
return true;
179+
} catch (error) {
180+
console.error(`${this.logHead} Connection error:`, error);
181+
gui_log(i18n.getMessage("bluetoothConnectionError", [error]));
174182

175-
console.log(
176-
`${this.logHead} Connection opened with ID: ${connectionInfo.connectionId}, but request was canceled, disconnecting`,
177-
);
178-
// some bluetooth dongles/dongle drivers really doesn't like to be closed instantly, adding a small delay
179-
setTimeout(() => {
180-
this.openRequested = false;
181-
this.openCanceled = false;
182-
this.disconnect(() => {
183-
this.dispatchEvent(new CustomEvent("connect", { detail: false }));
184-
});
185-
}, 150);
186-
} else if (this.openCanceled) {
187-
console.log(`${this.logHead} Connection didn't open and request was canceled`);
183+
// Clean up any partial connection state
188184
this.openRequested = false;
189185
this.openCanceled = false;
186+
187+
// Signal connection failure
190188
this.dispatchEvent(new CustomEvent("connect", { detail: false }));
191-
} else {
192-
this.openRequested = false;
193-
console.log(`${this.logHead} Failed to open bluetooth port`);
194-
this.dispatchEvent(new CustomEvent("connect", { detail: false }));
189+
return false;
195190
}
196191
}
197192

@@ -221,47 +216,84 @@ class WebBluetooth extends EventTarget {
221216
}
222217

223218
async getCharacteristics() {
224-
const characteristics = await this.service.getCharacteristics();
219+
try {
220+
const characteristics = await this.service.getCharacteristics();
225221

226-
characteristics.forEach((characteristic) => {
227-
// console.log("Characteristic: ", characteristic);
228-
if (characteristic.uuid == this.deviceDescription.writeCharacteristic) {
229-
this.writeCharacteristic = characteristic;
222+
if (!characteristics || characteristics.length === 0) {
223+
throw new Error("No characteristics found");
230224
}
231225

232-
if (characteristic.uuid == this.deviceDescription.readCharacteristic) {
233-
this.readCharacteristic = characteristic;
226+
// Reset characteristics
227+
this.writeCharacteristic = null;
228+
this.readCharacteristic = null;
229+
230+
// Collect all matching characteristics first without breaking early
231+
const writeMatches = [];
232+
const readMatches = [];
233+
234+
for (const characteristic of characteristics) {
235+
if (characteristic.uuid === this.deviceDescription.writeCharacteristic) {
236+
writeMatches.push(characteristic);
237+
}
238+
239+
if (characteristic.uuid === this.deviceDescription.readCharacteristic) {
240+
readMatches.push(characteristic);
241+
}
234242
}
235-
return this.writeCharacteristic && this.readCharacteristic;
236-
});
237243

238-
if (!this.writeCharacteristic) {
239-
throw new Error(
240-
"Unexpected write characteristic found - should be",
241-
this.deviceDescription.writeCharacteristic,
242-
);
243-
}
244+
// Select the first match of each type
245+
if (writeMatches.length > 0) {
246+
this.writeCharacteristic = writeMatches[0];
247+
if (writeMatches.length > 1) {
248+
console.warn(`${this.logHead} Multiple write characteristics found, using first match`);
249+
}
250+
}
244251

245-
if (!this.readCharacteristic) {
246-
throw new Error(
247-
"Unexpected read characteristic found - should be",
248-
this.deviceDescription.readCharacteristic,
249-
);
250-
}
252+
if (readMatches.length > 0) {
253+
this.readCharacteristic = readMatches[0];
254+
if (readMatches.length > 1) {
255+
console.warn(`${this.logHead} Multiple read characteristics found, using first match`);
256+
}
257+
}
251258

252-
this.readCharacteristic.addEventListener("characteristicvaluechanged", this.handleNotification.bind(this));
259+
if (!this.writeCharacteristic) {
260+
throw new Error(`Write characteristic not found: ${this.deviceDescription.writeCharacteristic}`);
261+
}
253262

254-
return await this.readCharacteristic.readValue();
263+
if (!this.readCharacteristic) {
264+
throw new Error(`Read characteristic not found: ${this.deviceDescription.readCharacteristic}`);
265+
}
266+
267+
// Use the bound method for the event listener
268+
this.readCharacteristic.addEventListener("characteristicvaluechanged", this.boundHandleNotification);
269+
270+
return await this.readCharacteristic.readValue();
271+
} catch (error) {
272+
console.error(`${this.logHead} Error getting characteristics:`, error);
273+
throw error;
274+
}
255275
}
256276

257277
handleNotification(event) {
258-
const buffer = new Uint8Array(event.target.value.byteLength);
278+
try {
279+
if (!event.target.value) {
280+
console.warn(`${this.logHead} Empty notification received`);
281+
return;
282+
}
259283

260-
for (let i = 0; i < event.target.value.byteLength; i++) {
261-
buffer[i] = event.target.value.getUint8(i);
262-
}
284+
const buffer = new Uint8Array(event.target.value.byteLength);
285+
286+
// Copy data with validation
287+
for (let i = 0; i < event.target.value.byteLength; i++) {
288+
buffer[i] = event.target.value.getUint8(i);
289+
}
263290

264-
this.dispatchEvent(new CustomEvent("receive", { detail: buffer }));
291+
if (buffer.length) {
292+
this.dispatchEvent(new CustomEvent("receive", { detail: buffer }));
293+
}
294+
} catch (error) {
295+
console.error(`${this.logHead} Error handling notification:`, error);
296+
}
265297
}
266298

267299
startNotifications() {
@@ -287,48 +319,51 @@ class WebBluetooth extends EventTarget {
287319
return;
288320
}
289321

290-
const doCleanup = async () => {
291-
this.removeEventListener("receive", this.handleReceiveBytes);
322+
this.closeRequested = true; // Set this to prevent reentry
323+
324+
try {
325+
this.removeEventListener("receive", this.boundHandleReceiveBytes);
292326

293327
if (this.device) {
294-
this.device.removeEventListener("disconnect", this.handleDisconnect.bind(this));
295-
this.device.removeEventListener("gattserverdisconnected", this.handleDisconnect);
296-
this.readCharacteristic.removeEventListener(
297-
"characteristicvaluechanged",
298-
this.handleNotification.bind(this),
299-
);
300-
301-
if (this.device.gatt.connected) {
302-
this.device.gatt.disconnect();
328+
// Use the properly bound references
329+
this.device.removeEventListener("disconnect", this.boundHandleDisconnect);
330+
this.device.removeEventListener("gattserverdisconnected", this.boundHandleDisconnect);
331+
332+
if (this.readCharacteristic) {
333+
try {
334+
// Stop notifications first to avoid errors
335+
await this.readCharacteristic.stopNotifications();
336+
this.readCharacteristic.removeEventListener(
337+
"characteristicvaluechanged",
338+
this.boundHandleNotification,
339+
);
340+
} catch (err) {
341+
console.warn(`${this.logHead} Error stopping notifications:`, err);
342+
}
303343
}
304344

305-
this.writeCharacteristic = false;
306-
this.readCharacteristic = false;
307-
this.deviceDescription = false;
308-
this.device = null;
309-
}
310-
};
311-
312-
try {
313-
await doCleanup();
345+
// Safely disconnect GATT
346+
if (this.device.gatt?.connected) {
347+
await this.device.gatt.disconnect();
348+
}
314349

315-
console.log(
316-
`${this.logHead} Connection with ID: ${this.connectionId} closed, Sent: ${this.bytesSent} bytes, Received: ${this.bytesReceived} bytes`,
317-
);
350+
// Clear references
351+
this.writeCharacteristic = null;
352+
this.readCharacteristic = null;
353+
this.deviceDescription = null;
354+
}
318355

356+
console.log(`${this.logHead} Connection closed successfully`);
319357
this.connectionId = false;
320358
this.bitrate = 0;
359+
this.device = null;
321360
this.dispatchEvent(new CustomEvent("disconnect", { detail: true }));
322361
} catch (error) {
323-
console.error(error);
324-
console.error(
325-
`${this.logHead} Failed to close connection with ID: ${this.connectionId} closed, Sent: ${this.bytesSent} bytes, Received: ${this.bytesReceived} bytes`,
326-
);
362+
console.error(`${this.logHead} Error during disconnect:`, error);
327363
this.dispatchEvent(new CustomEvent("disconnect", { detail: false }));
328364
} finally {
329-
if (this.openCanceled) {
330-
this.openCanceled = false;
331-
}
365+
this.closeRequested = false;
366+
this.openCanceled = false;
332367
}
333368
}
334369

0 commit comments

Comments
 (0)