Skip to content

Commit

Permalink
Merge pull request #19 from thegecko/use-types
Browse files Browse the repository at this point in the history
Use types from @types/web-bluetooth
  • Loading branch information
thegecko authored Jan 4, 2020
2 parents 0e209c8 + 214ecc8 commit 4af544a
Show file tree
Hide file tree
Showing 17 changed files with 501 additions and 175 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,25 @@ https://webbluetoothcg.github.io/web-bluetooth/

### Events

- [x] availabilitychanged - specification unstable
#### Bluetooth

- [x] availabilitychanged

#### Bluetooth Device

- [x] gattserverdisconnected
- [x] characteristicvaluechanged
- [ ] advertisementreceived - specification unstable

#### Bluetooth Service

- [x] serviceadded
- [ ] servicechanged - unsupported in noble
- [ ] serviceremoved - unsupported in noble

#### Bluetooth Characteristic

- [x] characteristicvaluechanged

### Other

- [x] Device selector hook
Expand Down
2 changes: 1 addition & 1 deletion examples/eddystone.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ const decodeEddystone = view => {
while (true) {
try {
await bluetooth.requestDevice({
filters:[{ services:[ eddystoneUUID ] }]
filters: [{ services: [ eddystoneUUID ] }]
});
} catch (error) {
console.log(error);
Expand Down
2 changes: 1 addition & 1 deletion examples/heartrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const bluetooth = require("../").bluetooth;
console.log("Requesting Bluetooth Devices...");

const device = await bluetooth.requestDevice({
filters:[{ services:[ "heart_rate" ] }]
filters: [{ services: [ "heart_rate" ] }]
});
console.log(`Found device: ${device.name}`);

Expand Down
4 changes: 3 additions & 1 deletion examples/selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,9 @@ console.log("scanning...");

(async () => {
try {
const device = await bluetooth.requestDevice();
const device = await bluetooth.requestDevice({
acceptAllDevices: true
});
console.log("connecting...");

const server = await device.gatt.connect();
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
},
"dependencies": {
"@types/node": "^8.0.54",
"@types/web-bluetooth": "^0.0.5",
"@abandonware/noble": "thegecko/noble"
}
}
1 change: 0 additions & 1 deletion src/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { EventEmitter } from "events";
import { getCanonicalUUID } from "./helpers";
import { BluetoothDevice } from "./device";
import { BluetoothRemoteGATTService } from "./service";
import { BluetoothRemoteGATTDescriptor } from "./descriptor";
import { BluetoothRemoteGATTCharacteristic } from "./characteristic";
import * as noble from "@abandonware/noble";

Expand Down
172 changes: 103 additions & 69 deletions src/bluetooth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ import { EventDispatcher, TypedDispatcher } from "./dispatcher";
import { BluetoothDevice, BluetoothDeviceEvents } from "./device";
import { getServiceUUID } from "./helpers";
import { adapter, NobleAdapter } from "./adapter";
import { BluetoothRemoteGATTServiceEvents } from "./service";
import { W3CBluetooth } from "./interfaces";
import { DOMEvent } from "./events";

/**
* Bluetooth Options interface
Expand All @@ -50,64 +51,19 @@ export interface BluetoothOptions {
}

/**
* BluetoothLE Scan Filter Init interface
* @hidden
*/
export interface BluetoothLEScanFilterInit {
/**
* An array of service UUIDs to filter on
*/
services?: Array<string | number>;

/**
* The device name to filter on
*/
name?: string;

/**
* The device name prefix to filter on
*/
namePrefix?: string;

// Maps unsigned shorts to BluetoothDataFilters.
// object manufacturerData;
// Maps BluetoothServiceUUIDs to BluetoothDataFilters.
// object serviceData;
}

/**
* Request Device Options interface
*/
export interface RequestDeviceOptions {
/**
* An array of device filters to match
*/
filters?: Array<BluetoothLEScanFilterInit>;

/**
* An array of optional services to have access to
*/
optionalServices?: Array<string | number>;

/**
* Whether to accept all devices
*/
acceptAllDevices?: boolean;
}

/**
* Events raised by the Bluetooth class
*/
export interface BluetoothEvents extends BluetoothDeviceEvents, BluetoothRemoteGATTServiceEvents {
export interface BluetoothEvents extends BluetoothDeviceEvents {
/**
* Bluetooth Availability Changed event
*/
availabilitychanged: boolean;
availabilitychanged: Event;
}

/**
* Bluetooth class
*/
export class Bluetooth extends (EventDispatcher as new() => TypedDispatcher<BluetoothEvents>) {
export class Bluetooth extends (EventDispatcher as new() => TypedDispatcher<BluetoothEvents>) implements W3CBluetooth {

/**
* Bluetooth Availability Changed event
Expand All @@ -124,6 +80,69 @@ export class Bluetooth extends (EventDispatcher as new() => TypedDispatcher<Blue
private scanTime: number = 10.24 * 1000;
private scanner = null;

private _oncharacteristicvaluechanged: (ev: Event) => void;
public set oncharacteristicvaluechanged(fn: (ev: Event) => void) {
if (this._oncharacteristicvaluechanged) {
this.removeEventListener("characteristicvaluechanged", this._oncharacteristicvaluechanged);
}
this._oncharacteristicvaluechanged = fn;
this.addEventListener("characteristicvaluechanged", this._oncharacteristicvaluechanged);
}

private _onserviceadded: (ev: Event) => void;
public set onserviceadded(fn: (ev: Event) => void) {
if (this._onserviceadded) {
this.removeEventListener("serviceadded", this._onserviceadded);
}
this._onserviceadded = fn;
this.addEventListener("serviceadded", this._onserviceadded);
}

private _onservicechanged: (ev: Event) => void;
public set onservicechanged(fn: (ev: Event) => void) {
if (this._onservicechanged) {
this.removeEventListener("servicechanged", this._onservicechanged);
}
this._onservicechanged = fn;
this.addEventListener("servicechanged", this._onservicechanged);
}

private _onserviceremoved: (ev: Event) => void;
public set onserviceremoved(fn: (ev: Event) => void) {
if (this._onserviceremoved) {
this.removeEventListener("serviceremoved", this._onserviceremoved);
}
this._onserviceremoved = fn;
this.addEventListener("serviceremoved", this._onserviceremoved);
}

private _ongattserverdisconnected: (ev: Event) => void;
public set ongattserverdisconnected(fn: (ev: Event) => void) {
if (this._ongattserverdisconnected) {
this.removeEventListener("gattserverdisconnected", this._ongattserverdisconnected);
}
this._ongattserverdisconnected = fn;
this.addEventListener("gattserverdisconnected", this._ongattserverdisconnected);
}

private _onadvertisementreceived: (ev: Event) => void;
public set onadvertisementreceived(fn: (ev: Event) => void) {
if (this._onadvertisementreceived) {
this.removeEventListener("advertisementreceived", this._onadvertisementreceived);
}
this._onadvertisementreceived = fn;
this.addEventListener("advertisementreceived", this._onadvertisementreceived);
}

private _onavailabilitychanged: (ev: Event) => void;
public set onavailabilitychanged(fn: (ev: Event) => void) {
if (this._onavailabilitychanged) {
this.removeEventListener("availabilitychanged", this._onavailabilitychanged);
}
this._onavailabilitychanged = fn;
this.addEventListener("availabilitychanged", this._onavailabilitychanged);
}

/**
* Bluetooth constructor
* @param options Bluetooth initialisation options
Expand All @@ -136,15 +155,15 @@ export class Bluetooth extends (EventDispatcher as new() => TypedDispatcher<Blue
this.deviceFound = options.deviceFound;
if (options.scanTime) this.scanTime = options.scanTime * 1000;

adapter.on(NobleAdapter.EVENT_ENABLED, value => {
this.dispatchEvent("availabilitychanged", value);
adapter.on(NobleAdapter.EVENT_ENABLED, _value => {
this.dispatchEvent(new DOMEvent(this, "availabilitychanged"));
});
}

private filterDevice(options: RequestDeviceOptions, deviceInfo, validServices) {
private filterDevice(filters: Array<BluetoothRequestDeviceFilter>, deviceInfo, validServices) {
let valid = false;

options.filters.forEach(filter => {
filters.forEach(filter => {
// Name
if (filter.name && filter.name !== deviceInfo.name) return;

Expand Down Expand Up @@ -189,15 +208,32 @@ export class Bluetooth extends (EventDispatcher as new() => TypedDispatcher<Blue
* @param options The options to use when scanning
* @returns Promise containing a device which matches the options
*/
public requestDevice(options?: RequestDeviceOptions): Promise<BluetoothDevice> {
public requestDevice(options: RequestDeviceOptions = { filters: [] }): Promise<BluetoothDevice> {
return new Promise((resolve, reject) => {
options = options || {};

if (this.scanner !== null) return reject("requestDevice error: request in progress");

if (!options.acceptAllDevices && !this.deviceFound) {
interface Filtered {
filters: Array<BluetoothRequestDeviceFilter>;
optionalServices?: Array<BluetoothServiceUUID>;
}

interface AcceptAll {
acceptAllDevices: boolean;
optionalServices?: Array<BluetoothServiceUUID>;
}

const isFiltered = (maybeFiltered: RequestDeviceOptions): maybeFiltered is Filtered =>
(maybeFiltered as Filtered).filters !== undefined;

const isAcceptAll = (maybeAcceptAll: RequestDeviceOptions): maybeAcceptAll is AcceptAll =>
(maybeAcceptAll as AcceptAll).acceptAllDevices === true;

let searchUUIDs = [];

if (isFiltered(options)) {
// Must have a filter
if (!options.filters || options.filters.length === 0) {
if (options.filters.length === 0) {
return reject(new TypeError("requestDevice error: no filters specified"));
}

Expand All @@ -216,21 +252,19 @@ export class Bluetooth extends (EventDispatcher as new() => TypedDispatcher<Blue
if (emptyPrefix) {
return reject(new TypeError("requestDevice error: empty namePrefix specified"));
}
}

let searchUUIDs = [];

if (options.filters) {
options.filters.forEach(filter => {
if (filter.services) searchUUIDs = searchUUIDs.concat(filter.services.map(getServiceUUID));

// Unique-ify
searchUUIDs = searchUUIDs.filter((item, index, array) => {
return array.indexOf(item) === index;
});
});
} else if (!isAcceptAll(options)) {
return reject(new TypeError("requestDevice error: specify filters or acceptAllDevices"));
}

// Unique-ify
searchUUIDs = searchUUIDs.filter((item, index, array) => {
return array.indexOf(item) === index;
});

let found = false;
adapter.startScan(searchUUIDs, deviceInfo => {
let validServices = [];
Expand All @@ -243,8 +277,8 @@ export class Bluetooth extends (EventDispatcher as new() => TypedDispatcher<Blue
}

// filter devices if filters specified
if (options.filters) {
deviceInfo = this.filterDevice(options, deviceInfo, validServices);
if (isFiltered(options)) {
deviceInfo = this.filterDevice(options.filters, deviceInfo, validServices);
}

if (deviceInfo) {
Expand Down
Loading

0 comments on commit 4af544a

Please sign in to comment.