Skip to content

Commit

Permalink
feat(all): add getDevices and getConnectedDevices
Browse files Browse the repository at this point in the history
  • Loading branch information
pwespi committed Aug 21, 2021
1 parent 965ff3d commit 768745c
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 27 deletions.
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
build
dist
dist
coverage/
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ Below is an index of all the methods available.
- [`requestDevice(...)`](#requestdevice)
- [`requestLEScan(...)`](#requestlescan)
- [`stopLEScan()`](#stoplescan)
- [`getDevices(...)`](#getdevices)
- [`getConnectedDevices(...)`](#getconnecteddevices)
- [`connect(...)`](#connect)
- [`createBond(...)`](#createbond)
- [`isBonded(...)`](#isbonded)
Expand Down Expand Up @@ -364,6 +366,44 @@ Stop scanning for BLE devices. For an example, see [usage](#usage).

---

### getDevices(...)

```typescript
getDevices(deviceIds: string[]) => Promise<BleDevice[]>
```

On iOS and web, if you want to connect to a previously connected device without scanning first, you can use `getDevice`.
Uses [retrievePeripherals](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1519127-retrieveperipherals) on iOS and
[getDevices](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/getDevices) on web.
On Android, you can directly connect to the device with the deviceId.

| Param | Type | Description |
| --------------- | --------------------- | ----------------------------------------------------------------------- |
| **`deviceIds`** | <code>string[]</code> | List of device IDs, e.g. saved from a previous app run. No used on web. |

**Returns:** <code>Promise&lt;BleDevice[]&gt;</code>

---

### getConnectedDevices(...)

```typescript
getConnectedDevices(services: string[]) => Promise<BleDevice[]>
```

Get a list of currently connected devices.
Uses [retrieveConnectedPeripherals](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1518924-retrieveconnectedperipherals) on iOS,
[getConnectedDevices](<https://developer.android.com/reference/android/bluetooth/BluetoothManager#getConnectedDevices(int)>) on Android
and [getDevices](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/getDevices) on web.

| Param | Type | Description |
| -------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| **`services`** | <code>string[]</code> | List of services to filter the devices by. If no service is specified, no devices will be returned. Only applies to iOS. |

**Returns:** <code>Promise&lt;BleDevice[]&gt;</code>

---

### connect(...)

```typescript
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.capacitorjs.community.plugins.bluetoothle

import android.Manifest
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothManager
import android.bluetooth.*
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
Expand Down Expand Up @@ -261,6 +258,36 @@ class BluetoothLe : Plugin() {
call.resolve()
}

@PluginMethod
fun getDevices(call: PluginCall) {
assertBluetoothAdapter(call) ?: return
val deviceIds = call.getArray("deviceIds").toList<String>()
var bleDevices = JSArray()
deviceIds.forEach { deviceId ->
val bleDevice = JSObject()
bleDevice.put("deviceId", deviceId)
bleDevices.put(bleDevice)
}
val result = JSObject()
result.put("devices", bleDevices)
call.resolve(result)
}

@PluginMethod
fun getConnectedDevices(call: PluginCall) {
assertBluetoothAdapter(call) ?: return
val bluetoothManager = (activity.getSystemService(Context.BLUETOOTH_SERVICE)
as BluetoothManager)
val devices = bluetoothManager.getConnectedDevices(BluetoothProfile.GATT)
val bleDevices = JSArray()
devices.forEach { device ->
bleDevices.put(getBleDevice(device))
}
val result = JSObject()
result.put("devices", bleDevices)
call.resolve(result)
}

@PluginMethod
fun connect(call: PluginCall) {
val device = getOrCreateDevice(call) ?: return
Expand Down
22 changes: 19 additions & 3 deletions ios/Plugin/DeviceManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,21 @@ class DeviceManager: NSObject, CBCentralManagerDelegate {
self?.viewController?.present((self?.alertController)!, animated: true, completion: nil)
}
}

func getDevice(_ deviceId: String) -> Device? {
return self.discoveredDevices[deviceId]

func getDevices(_ deviceUUIDs: [UUID]) -> [Device] {
let peripherals = self.centralManager.retrievePeripherals(withIdentifiers: deviceUUIDs)
let devices = peripherals.map({peripheral in
return Device(peripheral)
})
return devices
}

func getConnectedDevices(_ serviceUUIDs: [CBUUID]) -> [Device] {
let peripherals = self.centralManager.retrieveConnectedPeripherals(withServices: serviceUUIDs)
let devices = peripherals.map({peripheral in
return Device(peripheral)
})
return devices
}

func connect(_ device: Device, _ callback: @escaping Callback) {
Expand Down Expand Up @@ -239,6 +251,10 @@ class DeviceManager: NSObject, CBCentralManagerDelegate {
}
self.resolve(key, "Successfully disconnected.")
}

func getDevice(_ deviceId: String) -> Device? {
return self.discoveredDevices[deviceId]
}

private func passesNameFilter(peripheralName: String?) -> Bool {
guard let nameFilter = self.deviceNameFilter else { return true }
Expand Down
2 changes: 2 additions & 0 deletions ios/Plugin/Plugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
CAP_PLUGIN_METHOD(requestDevice, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(requestLEScan, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(stopLEScan, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getDevices, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(getConnectedDevices, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(connect, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(createBond, CAPPluginReturnPromise);
CAP_PLUGIN_METHOD(isBonded, CAPPluginReturnPromise);
Expand Down
41 changes: 38 additions & 3 deletions ios/Plugin/Plugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CoreBluetooth

@objc(BluetoothLe)
public class BluetoothLe: CAPPlugin {
typealias BleDevice = [String: Any]
private var deviceManager: DeviceManager?
private var deviceMap = [String: Device]()
private var displayStrings = [String: String]()
Expand Down Expand Up @@ -73,7 +74,7 @@ public class BluetoothLe: CAPPlugin {
return
}
self.deviceMap[device.getId()] = device
let bleDevice = self.getBleDevice(device)
let bleDevice: BleDevice = self.getBleDevice(device)
call.resolve(bleDevice)
} else {
call.reject(message)
Expand Down Expand Up @@ -117,6 +118,40 @@ public class BluetoothLe: CAPPlugin {
deviceManager.stopScan()
call.resolve()
}

@objc func getDevices(_ call: CAPPluginCall) {
guard let deviceManager = self.getDeviceManager(call) else { return }
guard let deviceIds = call.getArray("deviceIds", String.self) else {
call.reject("deviceIds must be provided")
return
}
let deviceUUIDs: [UUID] = deviceIds.compactMap({ deviceId in
return UUID(uuidString: deviceId)
})
let devices: [Device] = deviceManager.getDevices(deviceUUIDs)
let bleDevices: [BleDevice] = devices.map({device in
self.deviceMap[device.getId()] = device
return self.getBleDevice(device)
})
call.resolve(["devices": bleDevices])
}

@objc func getConnectedDevices(_ call: CAPPluginCall) {
guard let deviceManager = self.getDeviceManager(call) else { return }
guard let services = call.getArray("services", String.self) else {
call.reject("services must be provided")
return
}
let serviceUUIDs: [CBUUID] = services.compactMap({ service in
return CBUUID(string: service)
})
let devices: [Device] = deviceManager.getConnectedDevices(serviceUUIDs)
let bleDevices: [BleDevice] = devices.map({device in
self.deviceMap[device.getId()] = device
return self.getBleDevice(device)
})
call.resolve(["devices": bleDevices])
}

@objc func connect(_ call: CAPPluginCall) {
guard self.getDeviceManager(call) != nil else { return }
Expand Down Expand Up @@ -282,7 +317,7 @@ public class BluetoothLe: CAPPlugin {
return nil
}
guard let device = self.deviceMap[deviceId] else {
call.reject("Device not found. Call 'requestDevice' or 'requestLEScan' first.")
call.reject("Device not found. Call 'requestDevice', 'requestLEScan' or 'getDevices' first.")
return nil
}
if checkConnection {
Expand All @@ -309,7 +344,7 @@ public class BluetoothLe: CAPPlugin {
return (serviceUUID, characteristicUUID)
}

private func getBleDevice(_ device: Device) -> [String: Any] {
private func getBleDevice(_ device: Device) -> BleDevice {
var bleDevice = [
"deviceId": device.getId()
]
Expand Down
32 changes: 32 additions & 0 deletions src/bleClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,24 @@ export interface BleClientInterface {
*/
stopLEScan(): Promise<void>;

/**
* On iOS and web, if you want to connect to a previously connected device without scanning first, you can use `getDevice`.
* Uses [retrievePeripherals](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1519127-retrieveperipherals) on iOS and
* [getDevices](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/getDevices) on web.
* On Android, you can directly connect to the device with the deviceId.
* @param deviceIds List of device IDs, e.g. saved from a previous app run. No used on web.
*/
getDevices(deviceIds: string[]): Promise<BleDevice[]>;

/**
* Get a list of currently connected devices.
* Uses [retrieveConnectedPeripherals](https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1518924-retrieveconnectedperipherals) on iOS,
* [getConnectedDevices](https://developer.android.com/reference/android/bluetooth/BluetoothManager#getConnectedDevices(int)) on Android
* and [getDevices](https://developer.mozilla.org/en-US/docs/Web/API/Bluetooth/getDevices) on web.
* @param services List of services to filter the devices by. If no service is specified, no devices will be returned. Only applies to iOS.
*/
getConnectedDevices(services: string[]): Promise<BleDevice[]>;

/**
* Connect to a peripheral BLE device. For an example, see [usage](#usage).
* @param deviceId The ID of the device to use (obtained from [requestDevice](#requestDevice) or [requestLEScan](#requestLEScan))
Expand Down Expand Up @@ -241,6 +259,20 @@ class BleClientClass implements BleClientInterface {
});
}

async getDevices(deviceIds: string[]): Promise<BleDevice[]> {
return this.queue(async () => {
const result = await BluetoothLe.getDevices({ deviceIds });
return result.devices;
});
}

async getConnectedDevices(services: string[]): Promise<BleDevice[]> {
return this.queue(async () => {
const result = await BluetoothLe.getConnectedDevices({ services });
return result.devices;
});
}

async connect(deviceId: string, onDisconnect?: (deviceId: string) => void): Promise<void> {
await this.queue(async () => {
if (onDisconnect) {
Expand Down
14 changes: 14 additions & 0 deletions src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,14 @@ export interface DeviceIdOptions {
deviceId: string;
}

export interface GetDevicesOptions {
deviceIds: string[];
}

export interface GetConnectedDevicesOptions {
services: string[];
}

export interface ReadOptions {
deviceId: string;
service: string;
Expand All @@ -98,6 +106,10 @@ export interface BooleanResult {
value: boolean;
}

export interface GetDevicesResult {
devices: BleDevice[];
}

export interface ReadResult {
/**
* android, ios: string
Expand Down Expand Up @@ -163,6 +175,8 @@ export interface BluetoothLePlugin {
requestDevice(options?: RequestBleDeviceOptions): Promise<BleDevice>;
requestLEScan(options?: RequestBleDeviceOptions): Promise<void>;
stopLEScan(): Promise<void>;
getDevices(options: GetDevicesOptions): Promise<GetDevicesResult>;
getConnectedDevices(options: GetConnectedDevicesOptions): Promise<GetDevicesResult>;
addListener(eventName: 'onEnabledChanged', listenerFunc: (result: BooleanResult) => void): PluginListenerHandle;
addListener(eventName: string, listenerFunc: (event: ReadResult) => void): PluginListenerHandle;
addListener(eventName: 'onScanResult', listenerFunc: (result: ScanResultInternal) => void): PluginListenerHandle;
Expand Down
8 changes: 4 additions & 4 deletions src/web.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { BluetoothLe } from './web';

interface BluetoothLeWithPrivate extends BluetoothLePlugin {
deviceMap: Map<string, BluetoothDevice>;
discoverdDevices: Map<string, boolean>;
discoveredDevices: Map<string, boolean>;
scan: BluetoothLEScan | null;
onAdvertisemendReceived: (event: BluetoothAdvertisingEvent) => void;
onAdvertisementReceived: (event: BluetoothAdvertisingEvent) => void;
onDisconnected: (event: Event) => void;
}

Expand Down Expand Up @@ -87,11 +87,11 @@ describe('BluetoothLe web', () => {
await BluetoothLe.requestLEScan();
expect(mockBluetooth.removeEventListener).toHaveBeenCalledWith(
'advertisementreceived',
(BluetoothLe as unknown as BluetoothLeWithPrivate).onAdvertisemendReceived
(BluetoothLe as unknown as BluetoothLeWithPrivate).onAdvertisementReceived
);
expect(mockBluetooth.addEventListener).toHaveBeenCalledWith(
'advertisementreceived',
(BluetoothLe as unknown as BluetoothLeWithPrivate).onAdvertisemendReceived
(BluetoothLe as unknown as BluetoothLeWithPrivate).onAdvertisementReceived
);
expect(mockBluetooth.requestLEScan).toHaveBeenCalledWith({ filters: undefined, acceptAllAdvertisements: true });
});
Expand Down
Loading

0 comments on commit 768745c

Please sign in to comment.