2025-02-11 21:39:18 +13:00
|
|
|
import Constants from "../constants.js";
|
|
|
|
|
import Connection from "./connection.js";
|
|
|
|
|
|
2025-04-08 13:54:25 +12:00
|
|
|
class WebBleConnection extends Connection {
|
2025-02-11 21:39:18 +13:00
|
|
|
|
2026-02-18 05:49:46 -03:00
|
|
|
/** @param {any} bleDevice */
|
2025-02-11 21:39:18 +13:00
|
|
|
constructor(bleDevice) {
|
|
|
|
|
super();
|
|
|
|
|
this.bleDevice = bleDevice;
|
2026-02-18 05:49:46 -03:00
|
|
|
/** @type {any} */
|
2025-02-11 21:39:18 +13:00
|
|
|
this.gattServer = null;
|
2026-02-18 05:49:46 -03:00
|
|
|
/** @type {any} */
|
2025-02-11 21:39:18 +13:00
|
|
|
this.rxCharacteristic = null;
|
2026-02-18 05:49:46 -03:00
|
|
|
/** @type {any} */
|
2025-02-11 21:39:18 +13:00
|
|
|
this.txCharacteristic = null;
|
2026-01-23 23:05:22 +13:00
|
|
|
this.init();
|
2025-02-11 21:39:18 +13:00
|
|
|
}
|
|
|
|
|
|
2026-02-18 05:49:46 -03:00
|
|
|
/** @returns {Promise<WebBleConnection | null | undefined>} */
|
2025-02-11 21:39:18 +13:00
|
|
|
static async open() {
|
|
|
|
|
|
|
|
|
|
// ensure browser supports web bluetooth
|
2026-02-18 05:49:46 -03:00
|
|
|
// @ts-ignore - Web Bluetooth API
|
2025-02-11 21:39:18 +13:00
|
|
|
if(!navigator.bluetooth){
|
|
|
|
|
alert("Web Bluetooth is not supported in this browser");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ask user to select device
|
2026-02-18 05:49:46 -03:00
|
|
|
// @ts-ignore - Web Bluetooth API
|
2025-02-11 21:39:18 +13:00
|
|
|
const device = await navigator.bluetooth.requestDevice({
|
|
|
|
|
filters: [
|
|
|
|
|
{
|
|
|
|
|
services: [
|
|
|
|
|
Constants.Ble.ServiceUuid.toLowerCase(),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// make sure user selected a device
|
|
|
|
|
if(!device){
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-23 23:05:22 +13:00
|
|
|
return new WebBleConnection(device);
|
2025-02-11 21:39:18 +13:00
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async init() {
|
|
|
|
|
|
2025-02-11 23:40:34 +13:00
|
|
|
// listen for ble disconnect
|
|
|
|
|
this.bleDevice.addEventListener("gattserverdisconnected", () => {
|
2025-02-11 23:42:58 +13:00
|
|
|
this.onDisconnected();
|
2025-02-11 23:40:34 +13:00
|
|
|
});
|
|
|
|
|
|
2025-02-11 21:39:18 +13:00
|
|
|
// connect to gatt server
|
|
|
|
|
this.gattServer = await this.bleDevice.gatt.connect();
|
|
|
|
|
|
|
|
|
|
// find service
|
|
|
|
|
const service = await this.gattServer.getPrimaryService(Constants.Ble.ServiceUuid.toLowerCase());
|
|
|
|
|
const characteristics = await service.getCharacteristics();
|
|
|
|
|
|
|
|
|
|
// find rx characteristic (we write to this one, it's where the radio reads from)
|
|
|
|
|
this.rxCharacteristic = characteristics.find((characteristic) => {
|
|
|
|
|
return characteristic.uuid.toLowerCase() === Constants.Ble.CharacteristicUuidRx.toLowerCase();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// find tx characteristic (we read this one, it's where the radio writes to)
|
|
|
|
|
this.txCharacteristic = characteristics.find((characteristic) => {
|
|
|
|
|
return characteristic.uuid.toLowerCase() === Constants.Ble.CharacteristicUuidTx.toLowerCase();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// listen for frames from transmitted to us from the ble device
|
|
|
|
|
await this.txCharacteristic.startNotifications();
|
|
|
|
|
this.txCharacteristic.addEventListener('characteristicvaluechanged', (event) => {
|
|
|
|
|
const frame = new Uint8Array(event.target.value.buffer);
|
|
|
|
|
this.onFrameReceived(frame);
|
|
|
|
|
});
|
|
|
|
|
|
2025-02-11 23:30:54 +13:00
|
|
|
// fire connected event
|
2025-04-08 12:36:42 +12:00
|
|
|
await this.onConnected();
|
2025-02-11 23:30:54 +13:00
|
|
|
|
2025-02-11 21:39:18 +13:00
|
|
|
}
|
|
|
|
|
|
2026-02-18 05:49:46 -03:00
|
|
|
/** @returns {Promise<void>} */
|
2025-02-11 21:39:18 +13:00
|
|
|
async close() {
|
|
|
|
|
try {
|
|
|
|
|
this.gattServer?.disconnect();
|
|
|
|
|
this.gattServer = null;
|
|
|
|
|
} catch(e) {
|
|
|
|
|
// ignore error when disconnecting
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-18 05:49:46 -03:00
|
|
|
/**
|
|
|
|
|
* @param {Uint8Array} bytes
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
2025-02-11 21:39:18 +13:00
|
|
|
async write(bytes) {
|
|
|
|
|
try {
|
2025-02-11 21:49:37 +13:00
|
|
|
// fixme: NetworkError: GATT operation already in progress.
|
|
|
|
|
// todo: implement mutex to prevent multiple writes when another write is in progress
|
2025-02-11 21:39:18 +13:00
|
|
|
// we write to the rx characteristic, as that's where the radio reads from
|
|
|
|
|
await this.rxCharacteristic.writeValue(bytes);
|
|
|
|
|
} catch(e) {
|
|
|
|
|
console.log("failed to write to ble device", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-18 05:49:46 -03:00
|
|
|
/**
|
|
|
|
|
* @param {Uint8Array} frame
|
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
|
*/
|
2025-02-11 22:07:10 +13:00
|
|
|
async sendToRadioFrame(frame) {
|
|
|
|
|
this.emit("tx", frame);
|
|
|
|
|
await this.write(frame);
|
2025-02-11 21:39:18 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-08 13:54:25 +12:00
|
|
|
export default WebBleConnection;
|