// 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); }