Skip to content
Draft
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
39 changes: 29 additions & 10 deletions src/adapters/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,45 @@
* SOFTWARE.
*/

import type { BluetoothDevice } from '../device';
import type { BluetoothRemoteGATTService } from '../service';
import type { BluetoothRemoteGATTCharacteristic } from '../characteristic';
import type { BluetoothRemoteGATTDescriptor } from '../descriptor';
export interface BluetoothDeviceInit {
id: string;
name: string;
_serviceUUIDs: Array<string>;
_adData: Partial<BluetoothAdvertisingEvent>;
}

export interface BluetoothRemoteGATTServiceInit {
_handle: string;
uuid: string;
isPrimary: boolean;
}

export interface BluetoothRemoteGATTCharacteristicInit {
_handle: string;
uuid: string;
properties: BluetoothCharacteristicProperties;
}

export interface BluetoothRemoteGATTDescriptorInit {
_handle: string;
uuid: string;
}

/**
* @hidden
*/
export interface Adapter extends EventTarget {
getEnabled: () => Promise<boolean>;
getAdapters: () => Array<{ index: number, address: string, active: boolean}>;
getAdapters: () => Array<{ index: number, address: string, active: boolean }>;
useAdapter: (index: number) => void;
startScan: (serviceUUIDs: Array<string>, foundFn: (device: Partial<BluetoothDevice>) => void) => Promise<void>;
startScan: (serviceUUIDs: Array<string>, foundFn: (device: BluetoothDeviceInit) => void) => Promise<void>;
stopScan: () => void;
connect: (handle: string, disconnectFn?: () => void) => Promise<void>;
disconnect: (handle: string) => Promise<void>;
discoverServices: (handle: string, serviceUUIDs?: Array<string>) => Promise<Array<Partial<BluetoothRemoteGATTService>>>;
discoverIncludedServices: (handle: string, serviceUUIDs?: Array<string>) => Promise<Array<Partial<BluetoothRemoteGATTService>>>;
discoverCharacteristics: (handle: string, characteristicUUIDs?: Array<string>) => Promise<Array<Partial<BluetoothRemoteGATTCharacteristic>>>;
discoverDescriptors: (handle: string, descriptorUUIDs?: Array<string>) => Promise<Array<Partial<BluetoothRemoteGATTDescriptor>>>;
discoverServices: (handle: string, serviceUUIDs?: Array<string>) => Promise<Array<BluetoothRemoteGATTServiceInit>>;
discoverIncludedServices: (handle: string, serviceUUIDs?: Array<string>) => Promise<Array<BluetoothRemoteGATTServiceInit>>;
discoverCharacteristics: (handle: string, characteristicUUIDs?: Array<string>) => Promise<Array<BluetoothRemoteGATTCharacteristicInit>>;
discoverDescriptors: (handle: string, descriptorUUIDs?: Array<string>) => Promise<Array<BluetoothRemoteGATTDescriptorInit>>;
readCharacteristic: (handle: string) => Promise<DataView>;
writeCharacteristic: (handle: string, value: DataView, withoutResponse?: boolean) => Promise<void>;
enableNotify: (handle: string, notifyFn: (value: DataView) => void) => Promise<void>;
Expand Down
136 changes: 100 additions & 36 deletions src/adapters/simpleble-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,8 @@
* SOFTWARE.
*/

import { Adapter as BluetoothAdapter } from './adapter';
import { Adapter as BluetoothAdapter, BluetoothDeviceInit, BluetoothRemoteGATTServiceInit, BluetoothRemoteGATTCharacteristicInit, BluetoothRemoteGATTDescriptorInit } from './adapter';
import { BluetoothUUID } from '../uuid';
import { BluetoothDevice } from '../device';
import { BluetoothRemoteGATTCharacteristic } from '../characteristic';
import { BluetoothRemoteGATTService } from '../service';
import { BluetoothRemoteGATTDescriptor } from '../descriptor';
import {
isEnabled,
getAdapters as simpleBleAdapters,
Expand Down Expand Up @@ -133,10 +129,23 @@ class PeripheralHandles {
}

const peripheralHandle = this.parents.get(serviceHandle);
if (!peripheralHandle) {
throw new Error('Peripheral not found for service');
}

const peripheral = this.peripherals.get(peripheralHandle);
if (!peripheral) {
throw new Error('Peripheral not found for service');
}

const service = this.services.get(serviceHandle);
if (!service) {
throw new Error('Service not found');
}

return {
peripheral: this.peripherals.get(peripheralHandle!)!,
service: this.services.get(serviceHandle)!,
peripheral,
service,
characteristics
};
}
Expand All @@ -159,25 +168,78 @@ class PeripheralHandles {

public getCharacteristicGraph(characteristicHandle: string): { peripheral: Peripheral, service: Service, characteristic: Characteristic } {
const serviceHandle = this.parents.get(characteristicHandle);
const peripheralHandle = this.parents.get(serviceHandle!);
if (!serviceHandle) {
throw new Error('Service not found for characteristic');
}

const peripheralHandle = this.parents.get(serviceHandle);
if (!peripheralHandle) {
throw new Error('Peripheral not found for characteristic');
}

const peripheral = this.peripherals.get(peripheralHandle);
if (!peripheral) {
throw new Error('Peripheral not found for characteristic');
}

const service = this.services.get(serviceHandle);
if (!service) {
throw new Error('Service not found for characteristic');
}

const characteristic = this.characteristics.get(characteristicHandle);
if (!characteristic) {
throw new Error('Characteristic not found');
}

return {
peripheral: this.peripherals.get(peripheralHandle!)!,
service: this.services.get(serviceHandle!)!,
characteristic: this.characteristics.get(characteristicHandle)!
peripheral,
service,
characteristic
};
}

public getDescriptorGraph(descriptorHandle: string): { peripheral: Peripheral, service: Service, characteristic: Characteristic, descriptor: Descriptor } {
const characteristicHandle = this.parents.get(descriptorHandle);
const serviceHandle = this.parents.get(characteristicHandle!);
const peripheralHandle = this.parents.get(serviceHandle!);
if (!characteristicHandle) {
throw new Error('Characteristic not found for descriptor');
}

const serviceHandle = this.parents.get(characteristicHandle);
if (!serviceHandle) {
throw new Error('Service not found for descriptor');
}

const peripheralHandle = this.parents.get(serviceHandle);
if (!peripheralHandle) {
throw new Error('Peripheral not found for descriptor');
}

const peripheral = this.peripherals.get(peripheralHandle);
if (!peripheral) {
throw new Error('Peripheral not found for descriptor');
}

const service = this.services.get(serviceHandle);
if (!service) {
throw new Error('Service not found for descriptor');
}

const characteristic = this.characteristics.get(characteristicHandle);
if (!characteristic) {
throw new Error('Characteristic not found for descriptor');
}

const descriptor = this.descriptors.get(descriptorHandle);
if (!descriptor) {
throw new Error('Descriptor not found');
}

return {
peripheral: this.peripherals.get(peripheralHandle!)!,
service: this.services.get(serviceHandle!)!,
characteristic: this.characteristics.get(characteristicHandle!)!,
descriptor: this.descriptors.get(descriptorHandle)!
peripheral,
service,
characteristic,
descriptor
};
}
}
Expand All @@ -190,7 +252,7 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter {
private peripherals = new Map<string, Peripheral>();
private handles = new PeripheralHandles(this.peripherals);

private validDevice(device: Partial<BluetoothDevice>, serviceUUIDs: Array<string>): boolean {
private validDevice(device: BluetoothDeviceInit, serviceUUIDs: Array<string>): boolean {
if (serviceUUIDs.length === 0) {
// Match any device
return true;
Expand All @@ -207,13 +269,12 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter {
return serviceUUIDs.some(serviceUUID => advertisedUUIDs.indexOf(serviceUUID) >= 0);
}

private buildBluetoothDevice(device: Peripheral): Partial<BluetoothDevice> {
private buildBluetoothDevice(device: Peripheral): BluetoothDeviceInit {
const name = device.identifier;
const address = device.address;

const rssi = device.rssi;
const txPower = device.txPower;
const mtu = device.mtu;

const id = address || `${name}`;

Expand All @@ -239,7 +300,6 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter {
_adData: {
rssi,
txPower,
mtu,
serviceData,
manufacturerData
}
Expand Down Expand Up @@ -269,7 +329,7 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter {
this.adapter = selected;
}

public async startScan(serviceUUIDs: Array<string>, foundFn: (device: Partial<BluetoothDevice>) => void): Promise<void> {
public async startScan(serviceUUIDs: Array<string>, foundFn: (device: BluetoothDeviceInit) => void): Promise<void> {
if (this.state === false) {
throw new Error('adapter not enabled');
}
Expand All @@ -282,9 +342,9 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter {
this.adapter.setCallbackOnScanFound(peripheral => {
const device = this.buildBluetoothDevice(peripheral);
if (this.validDevice(device, serviceUUIDs)) {
if (!foundPeripherals.includes(device.id!)) {
foundPeripherals.push(device.id!);
this.peripherals.set(device.id!, peripheral);
if (!foundPeripherals.includes(device.id)) {
foundPeripherals.push(device.id);
this.peripherals.set(device.id, peripheral);
// Only call the found function the first time we find a valid device
foundFn(device);
}
Expand Down Expand Up @@ -343,10 +403,10 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter {
this.handles.deleteHandles(peripheral);
}

public async discoverServices(handle: string, serviceUUIDs?: Array<string>): Promise<Array<Partial<BluetoothRemoteGATTService>>> {
public async discoverServices(handle: string, serviceUUIDs?: Array<string>): Promise<Array<BluetoothRemoteGATTServiceInit>> {
const services = this.handles.getServices(handle);

const discovered: Partial<BluetoothRemoteGATTService>[] = [];
const discovered: BluetoothRemoteGATTServiceInit[] = [];
for (const [handle, service] of Object.entries(services)) {
if (!serviceUUIDs || serviceUUIDs.length === 0 || serviceUUIDs.indexOf(service.uuid) >= 0) {
discovered.push({
Expand All @@ -360,15 +420,15 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter {
return discovered;
}

public async discoverIncludedServices(_handle: string, _serviceUUIDs?: Array<string>): Promise<Array<Partial<BluetoothRemoteGATTService>>> {
public async discoverIncludedServices(_handle: string, _serviceUUIDs?: Array<string>): Promise<Array<BluetoothRemoteGATTServiceInit>> {
// Currently not implemented
return [];
}

public async discoverCharacteristics(handle: string, characteristicUUIDs?: Array<string>): Promise<Array<Partial<BluetoothRemoteGATTCharacteristic>>> {
public async discoverCharacteristics(handle: string, characteristicUUIDs?: Array<string>): Promise<Array<BluetoothRemoteGATTCharacteristicInit>> {
const { peripheral, service, characteristics } = this.handles.getCharacteristics(handle);

const discovered: Partial<BluetoothRemoteGATTCharacteristic>[] = [];
const discovered: BluetoothRemoteGATTCharacteristicInit[] = [];

for (const [handle, characteristic] of Object.entries(characteristics)) {
const charUUID = BluetoothUUID.canonicalUUID(characteristic.uuid);
Expand All @@ -395,17 +455,21 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter {
if (characteristic.canIndicate) {
peripheral.indicate(service.uuid, charUUID, data => {
if (this.handles.characteristicEvents.has(handle)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.handles.characteristicEvents.get(handle)!(new DataView(data.buffer));
const event = this.handles.characteristicEvents.get(handle);
if (event) {
event(new DataView(data.buffer));
}
}
});
}

if (characteristic.canNotify) {
peripheral.notify(service.uuid, charUUID, data => {
if (this.handles.characteristicEvents.has(handle)) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.handles.characteristicEvents.get(handle)!(new DataView(data.buffer));
const event = this.handles.characteristicEvents.get(handle);
if (event) {
event(new DataView(data.buffer));
}
}
});
}
Expand All @@ -415,9 +479,9 @@ export class SimplebleAdapter extends EventTarget implements BluetoothAdapter {
return discovered;
}

public async discoverDescriptors(handle: string, descriptorUUIDs?: Array<string>): Promise<Array<Partial<BluetoothRemoteGATTDescriptor>>> {
public async discoverDescriptors(handle: string, descriptorUUIDs?: Array<string>): Promise<Array<BluetoothRemoteGATTDescriptorInit>> {
const descriptors = this.handles.getDescriptors(handle);
const discovered = [];
const discovered = new Array<BluetoothRemoteGATTDescriptorInit>();

for (const [handle, descriptor] of Object.entries(descriptors)) {
const descUUID = BluetoothUUID.canonicalUUID(descriptor);
Expand Down
24 changes: 8 additions & 16 deletions src/bluetooth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
*/

import { adapter, EVENT_ENABLED } from './adapters';
import { BluetoothDeviceInit } from './adapters/adapter';
import { BluetoothDevice } from './device';
import { BluetoothUUID } from './uuid';

Expand Down Expand Up @@ -190,7 +191,7 @@ class BluetoothImpl extends EventTarget implements Bluetooth {
}
}

private filterDevice(filters: Array<BluetoothLEScanFilter>, deviceInfo: Partial<BluetoothDevice>, validServices: string[]): Partial<BluetoothDevice> | undefined {
private filterDevice(filters: Array<BluetoothLEScanFilter>, deviceInfo: BluetoothDeviceInit, validServices: string[]): BluetoothDeviceInit | undefined {
let valid = false;

filters.forEach(filter => {
Expand All @@ -207,7 +208,7 @@ class BluetoothImpl extends EventTarget implements Bluetooth {
if (filter.services) {
const serviceUUIDs = filter.services.map(BluetoothUUID.getService);
const servicesValid = serviceUUIDs.every(serviceUUID => {
return (deviceInfo._serviceUUIDs!.indexOf(serviceUUID) > -1);
return (deviceInfo._serviceUUIDs.indexOf(serviceUUID) > -1);
});

if (!servicesValid) return;
Expand All @@ -216,7 +217,7 @@ class BluetoothImpl extends EventTarget implements Bluetooth {

// Service Data
if (filter.serviceData) {
if (!deviceInfo._adData?.serviceData) return;
if (!deviceInfo._adData.serviceData) return;
const services = [...deviceInfo._adData.serviceData.keys()];
for (const entry of filter.serviceData) {
if (!services.includes(entry.service)) return;
Expand All @@ -225,7 +226,7 @@ class BluetoothImpl extends EventTarget implements Bluetooth {

// Manufacturer Data
if (filter.manufacturerData) {
if (!deviceInfo._adData?.manufacturerData) return;
if (!deviceInfo._adData.manufacturerData) return;
const manufacturers = [...deviceInfo._adData.manufacturerData.keys()];
for (const entry of filter.manufacturerData) {
if (!manufacturers.includes(entry.companyIdentifier)) return;
Expand Down Expand Up @@ -351,12 +352,8 @@ class BluetoothImpl extends EventTarget implements Bluetooth {
const allowedServices = validServices.filter((item, index, array) => {
return array.indexOf(item) === index;
});
Object.assign(deviceInfo, {
_bluetooth: this,
_allowedServices: allowedServices
});

const bluetoothDevice = new BluetoothDevice(deviceInfo, () => this.forgetDevice(deviceInfo.id!));
const bluetoothDevice = new BluetoothDevice(deviceInfo, this, allowedServices, () => this.forgetDevice(deviceInfo.id));

const selectFn = () => {
complete.call(this, bluetoothDevice);
Expand Down Expand Up @@ -388,13 +385,8 @@ class BluetoothImpl extends EventTarget implements Bluetooth {
}, this.scanTime);

adapter.startScan([], deviceInfo => {
if (this.options?.allowAllDevices || this.allowedDevices.has(deviceInfo.id!)) {
Object.assign(deviceInfo, {
_bluetooth: this,
_allowedServices: []
});

const bluetoothDevice = new BluetoothDevice(deviceInfo, () => this.forgetDevice(deviceInfo.id!));
if (this.options.allowAllDevices || this.allowedDevices.has(deviceInfo.id)) {
const bluetoothDevice = new BluetoothDevice(deviceInfo, this, [], () => this.forgetDevice(deviceInfo.id));
devices.push(bluetoothDevice);
}
});
Expand Down
Loading