Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to Bluetooth connectivity #300

Merged
merged 3 commits into from
Mar 4, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,9 @@ <h1>Web Bluetooth not available!</h1>
flag. However be careful as it would be risky to browse the web with this flag turned
on as it enables many other experimental web platform features. Starting with Chromium
version 100, enable the <a href="about://flags/#enable-web-bluetooth">about://flags/#enable-web-bluetooth</a>
safer flag instead.</p>
safer flag instead. You can also enable Web Bluetooth Binding by enabling the
<a href="about://flags/#enable-web-bluetooth-new-permissions-backend">about://flags/#enable-web-bluetooth-new-permissions-backend</a>
flag instead of the experimental features if it is available.</p>
</div>
</section>
<section class="step">
Expand Down
100 changes: 58 additions & 42 deletions js/workflows/ble.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
*/

import {FileTransferClient} from '../common/ble-file-transfer.js';
import {CONNTYPE, CONNSTATE} from '../constants.js';
import {CONNTYPE} from '../constants.js';
import {Workflow} from './workflow.js';
import {GenericModal, DeviceInfoModal} from '../common/dialogs.js';
import {sleep, getUrlParam} from '../common/utilities.js';
import {sleep} from '../common/utilities.js';

const bleNusServiceUUID = 'adaf0001-4369-7263-7569-74507974686e';
const bleNusCharRXUUID = 'adaf0002-4369-7263-7569-74507974686e';
Expand Down Expand Up @@ -63,23 +63,23 @@ class BLEWorkflow extends Workflow {
bond: btnBond
};

btnRequestBluetoothDevice.addEventListener('click', async (event) => {
await this.onRequestBluetoothDeviceButtonClick(event);
});
btnBond.addEventListener('click', async (event) => {
await this.onBond(event);
});
btnReconnect.addEventListener('click', async (event) => {
await this.reconnectButtonHandler(event);
});
btnRequestBluetoothDevice.addEventListener('click', this.onRequestBluetoothDeviceButtonClick.bind(this));
btnBond.addEventListener('click', this.onBond.bind(this));
btnReconnect.addEventListener('click', this.reconnectButtonHandler.bind(this));

// Check if Web Bluetooth is available
if (!(await this.available() instanceof Error)) {
let stepOne;
if (stepOne = modal.querySelector('.step:first-of-type')) {
stepOne.classList.add("hidden");
}
const devices = await navigator.bluetooth.getDevices();
this.connectionStep(devices.length > 0 ? 2 : 1);
try {
const devices = await navigator.bluetooth.getDevices();
console.log(devices);
this.connectionStep(devices.length > 0 ? 2 : 1);
} catch (e) {
console.log("New Permissions backend for Web Bluetooth not enabled. Go to chrome://flags/#enable-web-bluetooth-new-permissions-backend to enable.", e);
}
} else {
modal.querySelectorAll('.step:not(:first-of-type)').forEach((stepItem) => {
stepItem.classList.add("hidden");
Expand All @@ -92,7 +92,9 @@ class BLEWorkflow extends Workflow {

async onSerialReceive(e) {;
// TODO: Make use of super.onSerialReceive() so that title can be extracted
this.writeToTerminal(this.decoder.decode(e.target.value.buffer, {stream: true}));
let output = this.decoder.decode(e.target.value.buffer, {stream: true});
console.log(output);
this.writeToTerminal(output);
}

async connectToSerial() {
Expand All @@ -102,6 +104,8 @@ class BLEWorkflow extends Workflow {
this.txCharacteristic = await this.serialService.getCharacteristic(bleNusCharTXUUID);
this.rxCharacteristic = await this.serialService.getCharacteristic(bleNusCharRXUUID);

// Remove any existing event listeners to prevent multiple reads
this.txCharacteristic.removeEventListener('characteristicvaluechanged', this.onSerialReceive.bind(this));
this.txCharacteristic.addEventListener('characteristicvaluechanged', this.onSerialReceive.bind(this));
await this.txCharacteristic.startNotifications();
return true;
Expand All @@ -118,92 +122,102 @@ class BLEWorkflow extends Workflow {
console.log('Getting existing permitted Bluetooth devices...');
const devices = await navigator.bluetooth.getDevices();

console.log('> Got ' + devices.length + ' Bluetooth devices.');
console.log('> Found ' + devices.length + ' Bluetooth device(s).');
// These devices may not be powered on or in range, so scan for
// advertisement packets from them before connecting.
for (const device of devices) {
await this.connectToBluetoothDevice(device);
}
}
catch (error) {
console.log('Argh! ' + error);
console.error(error);
await this._showMessage(error);
}
}
}

// Bring up a dialog to request a device
async requestDevice() {
return navigator.bluetooth.requestDevice({
filters: [{services: [0xfebb]},], // <- Prefer filters to save energy & show relevant devices.
optionalServices: [0xfebb, bleNusServiceUUID]
});
}

async connectToBluetoothDevice(device) {
const abortController = new AbortController();

device.addEventListener('advertisementreceived', async (event) => {
async function onAdvertisementReceived(event) {
console.log('> Received advertisement from "' + device.name + '"...');
// Stop watching advertisements to conserve battery life.
abortController.abort();
console.log('Connecting to GATT Server from "' + device.name + '"...');
try {
await this.showBusy(device.gatt.connect());
await device.gatt.connect();
} catch (error) {
await this._showMessage("Failed to connect to device. Try forgetting device from OS bluetooth devices and try again.");
}
if (device.gatt.connected) {
console.log('> Bluetooth device "' + device.name + ' connected.');
await this.switchToDevice(device);
} else {
console.log('Unable to connect to bluetooth device "' + device.name + '.');
}
catch (error) {
console.log('Argh! ' + error);
}
}, {once: true});
}

//await this.showBusy(device.gatt.connect());
await navigator.bluetooth.requestDevice({
filters: [{services: [0xfebb]},], // <- Prefer filters to save energy & show relevant devices.
optionalServices: [0xfebb, bleNusServiceUUID]
});
device.removeEventListener('advertisementreceived', onAdvertisementReceived.bind(this));
device.addEventListener('advertisementreceived', onAdvertisementReceived.bind(this));

this.debugLog("connecting to " + device.name);
try {
console.log('Watching advertisements from "' + device.name + '"...');
await device.watchAdvertisements({signal: abortController.signal});
}
catch (error) {
console.log('Argh! ' + error);
console.error(error);
await this._showMessage(error);
}
}

// Request Bluetooth Device
async onRequestBluetoothDeviceButtonClick(e) {
try {
//try {
console.log('Requesting any Bluetooth device...');
this.debugLog("Requesting device. Cancel if empty and try existing");
let device = await navigator.bluetooth.requestDevice({
filters: [{services: [0xfebb]},], // <- Prefer filters to save energy & show relevant devices.
optionalServices: [0xfebb, bleNusServiceUUID]
});
let device = await this.requestDevice();

await this.showBusy(device.gatt.connect());
console.log('> Requested ' + device.name);
await device.gatt.connect();

await this.switchToDevice(device);
}
/*}
catch (error) {
console.log('Argh: ' + error);
console.error(error);
await this._showMessage(error);
this.debugLog('No device selected. Try to connect to existing.');
}
}*/
}

async switchToDevice(device) {
console.log(device);
this.bleDevice = device;
this.bleDevice.removeEventListener("gattserverdisconnected", this.onDisconnected.bind(this));
this.bleDevice.addEventListener("gattserverdisconnected", this.onDisconnected.bind(this));
this.bleServer = this.bleDevice.gatt;
console.log("connected", this.bleServer);
let services;

try {
console.log(device.gatt.connected);
//try {
services = await this.bleServer.getPrimaryServices();
} catch (e) {
/*} catch (e) {
console.log(e, e.stack);
}
}*/
console.log(services);

console.log('Initializing File Transfer Client...');
this.initFileClient(new FileTransferClient(this.bleDevice, 65536));
this.debugLog("connected");
await this.fileHelper.bond();
await this.connectToSerial();

// Enable/Disable UI buttons
Expand Down Expand Up @@ -256,15 +270,17 @@ class BLEWorkflow extends Workflow {
if (result = await super.connect() instanceof Error) {
return result;
}
// Is this a new connection?
if (!this.bleDevice) {
let devices = await navigator.bluetooth.getDevices();
for (const device of devices) {
await this.connectToBluetoothDevice(device);
}
}

// Do we have a connection now but still need to connect serial?
if (this.bleDevice && !this.bleServer) {
await await this.showBusy(this.bleDevice.gatt.connect());
await this.showBusy(this.bleDevice.gatt.connect());
this.switchToDevice(this.bleDevice);
}
}
Expand Down
Loading