You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
137 lines
3.6 KiB
JavaScript
137 lines
3.6 KiB
JavaScript
// based on https://github.com/loginov-rocks/Web-Bluetooth-Terminal
|
|
class BLEConnection extends Connection {
|
|
static priority = 600;
|
|
static name = 'BLE';
|
|
|
|
#device;
|
|
#characteristic;
|
|
#buffer;
|
|
#packet_buffer;
|
|
|
|
constructor(hub) {
|
|
super(hub);
|
|
this.options.enabled = false;
|
|
this.options.buffer_size = 1024;
|
|
this.options.service_uuid = 0xFFE0;
|
|
this.options.characteristic_uuid = 0xFFE1;
|
|
this.options.max_size = 20;
|
|
this.options.max_retries = 3;
|
|
|
|
this.#packet_buffer = new PacketBufferScanAll(data => {
|
|
this.hub._parsePacket(this, data);
|
|
});
|
|
}
|
|
|
|
isConnected() {
|
|
return this.options.enabled && this.#device && this.#device.gatt.connected && this.#characteristic;
|
|
}
|
|
|
|
getName() {
|
|
return this.#device ? this.#device.name : null;
|
|
}
|
|
|
|
async begin() {
|
|
this.#buffer = new CyclicBuffer(this.options.buffer_size);
|
|
|
|
if (this.options.enabled)
|
|
await this.connect();
|
|
}
|
|
|
|
async select() {
|
|
await this.disconnect();
|
|
if (this.#device) {
|
|
this.#device.removeEventListener('gattserverdisconnected', this._disconnect_h);
|
|
this.#device = undefined;
|
|
}
|
|
this._setState(ConnectionState.CONNECTING);
|
|
|
|
try {
|
|
this.#device = await navigator.bluetooth.requestDevice({
|
|
filters: [{ services: [this.options.service_uuid] }],
|
|
});
|
|
this.#device.addEventListener('gattserverdisconnected', this._disconnect_h);
|
|
} finally {
|
|
this._setState(ConnectionState.DISCONNECTED);
|
|
}
|
|
}
|
|
|
|
async connect() {
|
|
if (!this.#device)
|
|
return;
|
|
|
|
await this.disconnect();
|
|
await this.#connect();
|
|
}
|
|
|
|
async disconnect() {
|
|
if (this.#characteristic) {
|
|
this.#characteristic.removeEventListener('characteristicvaluechanged', this._change_h);
|
|
try {
|
|
await this.#characteristic.stopNotifications();
|
|
} catch (e) {}
|
|
this.#characteristic = undefined;
|
|
}
|
|
|
|
if (this.#device && this.#device.gatt.connected) {
|
|
try {
|
|
this.#device.gatt.disconnect();
|
|
} catch (e) {}
|
|
}
|
|
this.#buffer.clear();
|
|
this._setState(ConnectionState.DISCONNECTED);
|
|
}
|
|
|
|
async send(data) {
|
|
data = new TextEncoder().encode(data);
|
|
this.#buffer.push(data);
|
|
|
|
while (this.isConnected() && !this.#buffer.isEmpty()) {
|
|
const data = this.#buffer.pop(this.options.max_size);
|
|
const device = this.#device;
|
|
let retry = 0;
|
|
while (this.isConnected() && device == this.#device) {
|
|
try {
|
|
await this.#characteristic.writeValue(data);
|
|
break;
|
|
} catch (e) {
|
|
if (retry === this.options.max_retries)
|
|
throw e;
|
|
await sleep(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
async #connect() {
|
|
this._setState(ConnectionState.CONNECTING);
|
|
|
|
try {
|
|
let server = this.#device.gatt;
|
|
if (!server.connected) {
|
|
server = await server.connect();
|
|
}
|
|
|
|
const service = await server.getPrimaryService(this.options.service_uuid);
|
|
this.#characteristic = await service.getCharacteristic(this.options.characteristic_uuid);
|
|
await this.#characteristic.startNotifications();
|
|
this.#characteristic.addEventListener('characteristicvaluechanged', this._change_h);
|
|
|
|
} catch (e) {
|
|
this._setState(ConnectionState.DISCONNECTED);
|
|
throw e;
|
|
}
|
|
this._setState(ConnectionState.CONNECTED);
|
|
}
|
|
|
|
async _handleDisconnection(event) {
|
|
this._setState(ConnectionState.DISCONNECTED);
|
|
await this.#connect();
|
|
}
|
|
_disconnect_h = this._handleDisconnection.bind(this);
|
|
|
|
async _btchanged(event) {
|
|
if (event.target.value) this.#packet_buffer.push(event.target.value);
|
|
}
|
|
_change_h = this._btchanged.bind(this);
|
|
}
|