From 8a04ac34b4150a04c2136bd0182751c89bc3e16d Mon Sep 17 00:00:00 2001 From: Oleksandr Korneiko Date: Tue, 18 Oct 2022 10:42:10 +0300 Subject: [PATCH 01/86] fix: android 12 enable permissions call --- src/android/BLECentralPlugin.java | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index c3df495f..64f2d821 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -344,8 +344,19 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback } else if (action.equals(ENABLE)) { enableBluetoothCallback = callbackContext; - Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); - cordova.startActivityForResult(this, intent, REQUEST_ENABLE_BLUETOOTH); + + //check android12+ - perms should be asked in a bulk + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + String[] ANDROID_12_BLE_PERMISSIONS = new String[]{ + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.BLUETOOTH_CONNECT + }; + cordova.requestPermissions(this, REQUEST_ENABLE_BLUETOOTH, ANDROID_12_BLE_PERMISSIONS); + } + else{ + Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + cordova.startActivityForResult(this, intent, REQUEST_ENABLE_BLUETOOTH); + } } else if (action.equals(START_STATE_NOTIFICATIONS)) { @@ -1227,6 +1238,12 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, int } switch(requestCode) { + case REQUEST_ENABLE_BLUETOOTH: + LOG.d(TAG, "User enabled Bluetooth"); + if (enableBluetoothCallback != null) { + enableBluetoothCallback.success(); + } + break; case REQUEST_BLUETOOTH_SCAN: LOG.d(TAG, "User granted Bluetooth Scan Access"); findLowEnergyDevices(permissionCallback, serviceUUIDs, scanSeconds, scanSettings); From 58edfca0c850d09abe8124e4a55402a4ecbdb3d2 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 18 Oct 2022 21:26:34 +1100 Subject: [PATCH 02/86] Improve backwards compatibility for #941 Fixes #940 --- src/android/BLECentralPlugin.java | 39 +++++++++++++++++-------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 64f2d821..7dee77b2 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -343,20 +343,7 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback } else if (action.equals(ENABLE)) { - enableBluetoothCallback = callbackContext; - - //check android12+ - perms should be asked in a bulk - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - String[] ANDROID_12_BLE_PERMISSIONS = new String[]{ - Manifest.permission.BLUETOOTH_SCAN, - Manifest.permission.BLUETOOTH_CONNECT - }; - cordova.requestPermissions(this, REQUEST_ENABLE_BLUETOOTH, ANDROID_12_BLE_PERMISSIONS); - } - else{ - Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); - cordova.startActivityForResult(this, intent, REQUEST_ENABLE_BLUETOOTH); - } + enableBluetooth(callbackContext); } else if (action.equals(START_STATE_NOTIFICATIONS)) { @@ -552,6 +539,22 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback return validAction; } + private void enableBluetooth(CallbackContext callbackContext) { + if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter#ACTION_REQUEST_ENABLE + // Android 12+ requires BLUETOOTH_CONNECT in order to trigger an enable request + if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) { + permissionCallback = callbackContext; + PermissionHelper.requestPermission(this, REQUEST_ENABLE_BLUETOOTH, BLUETOOTH_CONNECT); + return; + } + } + + enableBluetoothCallback = callbackContext; + Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + cordova.startActivityForResult(this, intent, REQUEST_ENABLE_BLUETOOTH); + } + private void getBondedDevices(CallbackContext callbackContext) { if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) { @@ -1239,11 +1242,11 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, int switch(requestCode) { case REQUEST_ENABLE_BLUETOOTH: - LOG.d(TAG, "User enabled Bluetooth"); - if (enableBluetoothCallback != null) { - enableBluetoothCallback.success(); - } + LOG.d(TAG, "User granted Bluetooth Connect access for enable bluetooth"); + enableBluetooth(permissionCallback); + this.permissionCallback = null; break; + case REQUEST_BLUETOOTH_SCAN: LOG.d(TAG, "User granted Bluetooth Scan Access"); findLowEnergyDevices(permissionCallback, serviceUUIDs, scanSeconds, scanSettings); From 4821b40e35102b243c313855cc26908efab929ab Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 9 Aug 2022 22:27:19 +1000 Subject: [PATCH 03/86] Improve Android 11 background permissions request ordering --- src/android/BLECentralPlugin.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 7dee77b2..4a520a39 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -1081,6 +1081,19 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) { missingPermissions.add(BLUETOOTH_CONNECT); } + } else if (COMPILE_SDK_VERSION >= 30 && Build.VERSION.SDK_INT >= 30) { // (API 30) Build.VERSION_CODES.R + // Android 11 specifically requires FINE location access to be granted first before + // the app is allowed to ask for ACCESS_BACKGROUND_LOCATION + // Source: https://developer.android.com/about/versions/11/privacy/location + if (!PermissionHelper.hasPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)) { + missingPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); + } else { + String accessBackgroundLocation = this.preferences.getString("accessBackgroundLocation", "false"); + if (accessBackgroundLocation == "true" && !PermissionHelper.hasPermission(this, ACCESS_BACKGROUND_LOCATION)) { + LOG.w(TAG, "ACCESS_BACKGROUND_LOCATION is being requested"); + missingPermissions.add(ACCESS_BACKGROUND_LOCATION); + } + } } else if (COMPILE_SDK_VERSION >= 29 && Build.VERSION.SDK_INT >= 29) { // (API 29) Build.VERSION_CODES.Q if (!PermissionHelper.hasPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)) { missingPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); From ba0219f3f3a3e1446079398ba95243098a643d3a Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Thu, 11 Aug 2022 20:21:20 +1000 Subject: [PATCH 04/86] Document advanced android scan options --- types.d.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/types.d.ts b/types.d.ts index 46c603e7..adb48021 100644 --- a/types.d.ts +++ b/types.d.ts @@ -5,7 +5,7 @@ declare namespace BLECentralPlugin { service: string; characteristic: string; properties: string[]; - descriptors?: any[] | undefined; + descriptors?: any[]; } interface PeripheralData { @@ -28,7 +28,22 @@ declare namespace BLECentralPlugin { } interface StartScanOptions { - reportDuplicates?: boolean | undefined; + /* Android only */ + scanMode?: 'lowPower' | 'balanced' | 'lowLatency' | 'opportunistic'; + /* Android only */ + callbackType?: 'all' | 'first' | 'lost'; + /* Android only */ + matchMode?: 'aggressive' | 'sticky'; + /* Android only */ + numOfMatches?: 'one' | 'few' | 'max'; + /* Android only */ + phy?: '1m' | 'coded' | 'all'; + /* Android only */ + legacy?: boolean; + /* Android only */ + reportDelay?: number; + + reportDuplicates?: boolean; } interface L2CAPOptions { @@ -223,7 +238,7 @@ declare namespace BLECentralPlugin { service_uuid: string, characteristic_uuid: string, success: (rawData: ArrayBuffer | 'registered') => any, - failure?: (error: string | BLEError) => any, + failure: (error: string | BLEError) => any, options: { emitOnRegistered: boolean } ): void; From 09e05cf70fa235aa324f7cfa6d5ac4c69302d01e Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Fri, 14 Oct 2022 23:43:31 +1100 Subject: [PATCH 05/86] Prevent NPE if device disconnects before device cache refresh #936 Fixes #936 --- src/android/Peripheral.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/android/Peripheral.java b/src/android/Peripheral.java index 6ce85a1b..de18277a 100644 --- a/src/android/Peripheral.java +++ b/src/android/Peripheral.java @@ -229,11 +229,17 @@ public void refreshDeviceCache(CallbackContext callback, final long timeoutMilli if (success) { this.refreshCallback = callback; Handler handler = new Handler(); + LOG.d(TAG, "Waiting " + timeoutMillis + " milliseconds before discovering services"); handler.postDelayed(new Runnable() { @Override public void run() { - LOG.d(TAG, "Waiting " + timeoutMillis + " milliseconds before discovering services"); - gatt.discoverServices(); + if (gatt != null) { + try { + gatt.discoverServices(); + } catch(Exception e) { + LOG.e(TAG, "refreshDeviceCache Failed after delay", e); + } + } } }, timeoutMillis); } From 0f8a2f7182d9eaec8d429341bc3fe7e33bf850d1 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Fri, 14 Oct 2022 23:48:50 +1100 Subject: [PATCH 06/86] Prevent NPE after L2CAP socket disconnects --- src/android/L2CAPContext.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/android/L2CAPContext.java b/src/android/L2CAPContext.java index 1568be91..f81df043 100644 --- a/src/android/L2CAPContext.java +++ b/src/android/L2CAPContext.java @@ -110,9 +110,10 @@ public void writeL2CapChannel(CallbackContext callbackContext, byte[] data) { @RequiresApi(api = Build.VERSION_CODES.M) private void readL2CapData() { try { - InputStream inputStream = socket.getInputStream(); - byte[] buffer = new byte[socket.getMaxReceivePacketSize()]; - while (socket.isConnected()) { + final BluetoothSocket lSocket = this.socket; + InputStream inputStream = lSocket.getInputStream(); + byte[] buffer = new byte[lSocket.getMaxReceivePacketSize()]; + while (lSocket.isConnected()) { int readCount = inputStream.read(buffer); CallbackContext receiver; synchronized (updateLock) { From 6abdc1114733d6ecc4de13a2d3fbfa8827201d39 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 15 Oct 2022 00:05:13 +1100 Subject: [PATCH 07/86] Improve release documentation --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 85d0c1f0..5d91087a 100644 --- a/README.md +++ b/README.md @@ -1322,6 +1322,21 @@ Run the app on your phone cordova run android --device +# Release process + +## Pre-release + +1. `npm version prepatch --preid=alpha` +2. Align `plugin.xml` version with npm version +3. `npm publish --tag alpha --registry=https://registry.npmjs.org` + +## Release + +1. `npm version patch` +2. Align `plugin.xml` version with npm version +3. Update release notes +4. `npm publish --registry=https://registry.npmjs.org` + # Nordic DFU If you need Nordic DFU capability, Tomáš Bedřich has a [fork](https://github.com/fxe-gear/cordova-plugin-ble-central) of this plugin that adds an `updateFirmware()` method that allows users to upgrade nRF5x based chips over the air. https://github.com/fxe-gear/cordova-plugin-ble-central From 26a142dcc9a4330591cf2b90a32987bc897b8db8 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 15 Oct 2022 00:46:28 +1100 Subject: [PATCH 08/86] Android: Prevent various exceptions on stopScan #901 #871 Fixes #901 and #871 --- src/android/BLECentralPlugin.java | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 4a520a39..0feef265 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -117,7 +117,6 @@ public class BLECentralPlugin extends CordovaPlugin { private static final int REQUEST_ENABLE_BLUETOOTH = 1; BluetoothAdapter bluetoothAdapter; - BluetoothLeScanner bluetoothLeScanner; // key is the MAC Address Map peripherals = new LinkedHashMap(); @@ -193,7 +192,6 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback } BluetoothManager bluetoothManager = (BluetoothManager) activity.getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter(); - bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); } boolean validAction = true; @@ -212,8 +210,7 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback findLowEnergyDevices(callbackContext, serviceUUIDs, -1); } else if (action.equals(STOP_SCAN)) { - - bluetoothLeScanner.stopScan(leScanCallback); + stopScan(); callbackContext.success(); } else if (action.equals(LIST)) { @@ -1142,7 +1139,7 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic } discoverCallback = callbackContext; - bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); + final BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); List filters = new ArrayList(); if (serviceUUIDs != null && serviceUUIDs.length > 0) { for (UUID uuid : serviceUUIDs) { @@ -1158,8 +1155,7 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic handler.postDelayed(new Runnable() { @Override public void run() { - LOG.d(TAG, "Stopping Scan"); - bluetoothLeScanner.stopScan(leScanCallback); + stopScan(); } }, scanSeconds * 1000); } @@ -1169,6 +1165,19 @@ public void run() { callbackContext.sendPluginResult(result); } + private void stopScan() { + if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { + LOG.d(TAG, "Stopping Scan"); + try { + final BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); + if (bluetoothLeScanner != null) + bluetoothLeScanner.stopScan(leScanCallback); + } catch (Exception e) { + LOG.e(TAG, "Exception stopping scan", e); + } + } + } + private boolean locationServicesEnabled() { int locationMode = 0; try { From bd2734b85982862cbf0980f94ed3cffaac311523 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 15 Oct 2022 00:30:24 +1100 Subject: [PATCH 09/86] Android: Clear up time-based stopScan when new scan is started #902 Fixes #902 --- src/android/BLECentralPlugin.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 0feef265..b219c7c0 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -35,6 +35,7 @@ import android.content.pm.PackageManager; import android.content.IntentFilter; import android.os.Handler; +import android.os.Looper; import android.os.Build; import android.provider.Settings; @@ -135,6 +136,7 @@ public class BLECentralPlugin extends CordovaPlugin { private UUID[] serviceUUIDs; private int scanSeconds; private ScanSettings scanSettings; + private final Handler stopScanHandler = new Handler(Looper.getMainLooper()); // Bluetooth state notification CallbackContext stateCallback; @@ -1148,16 +1150,11 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic filters.add(filter); } } + stopScanHandler.removeCallbacks(this::stopScan); bluetoothLeScanner.startScan(filters, scanSettings, leScanCallback); if (scanSeconds > 0) { - Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - stopScan(); - } - }, scanSeconds * 1000); + stopScanHandler.postDelayed(this::stopScan, scanSeconds * 1000); } PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT); @@ -1166,6 +1163,7 @@ public void run() { } private void stopScan() { + stopScanHandler.removeCallbacks(this::stopScan); if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { LOG.d(TAG, "Stopping Scan"); try { From 4c2ceab242db08b71a609c1303d9c35e873d7a00 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 15 Oct 2022 00:55:26 +1100 Subject: [PATCH 10/86] Android: Report if Bluetooth is disabled when scanning/connecting #826 Fixes #826 --- src/android/BLECentralPlugin.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index b219c7c0..0dc4e5dc 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -709,6 +709,12 @@ private void connect(CallbackContext callbackContext, String macAddress) { } } + if (bluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) { + LOG.w(TAG, "Tried to connect while Bluetooth is disabled."); + callbackContext.error("Bluetooth is disabled."); + return; + } + if (!peripherals.containsKey(macAddress) && BLECentralPlugin.this.bluetoothAdapter.checkBluetoothAddress(macAddress)) { BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress); Peripheral peripheral = new Peripheral(device); @@ -737,6 +743,12 @@ private void autoConnect(CallbackContext callbackContext, String macAddress) { } } + if (bluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) { + LOG.w(TAG, "Tried to connect while Bluetooth is disabled."); + callbackContext.error("Bluetooth is disabled."); + return; + } + Peripheral peripheral = peripherals.get(macAddress); // allow auto-connect to connect to devices without scanning @@ -1120,6 +1132,12 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic } + if (bluetoothAdapter.getState() != BluetoothAdapter.STATE_ON) { + LOG.w(TAG, "Tried to start scan while Bluetooth is disabled."); + callbackContext.error("Bluetooth is disabled."); + return; + } + // return error if already scanning if (bluetoothAdapter.isDiscovering()) { LOG.w(TAG, "Tried to start scan while already running."); From 8d574ca5eb6a5e86765dda9c0a17d6c7aa1f9a1c Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 15 Oct 2022 01:11:12 +1100 Subject: [PATCH 11/86] Android: More carefully manage permission callbacks to avoid NPEs #773 #698 Should fix #773 and #698 --- src/android/BLECentralPlugin.java | 47 ++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 0dc4e5dc..36003635 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -1250,6 +1250,20 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { /* @Override */ public void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) { + final CallbackContext callback = this.popPermissionsCallback(); + if (callback == null) { + if (grantResults.length > 0) { + // There are some odd happenings if permission requests are made while booting up capacitor + LOG.w(TAG, "onRequestPermissionResult received with no pending callback"); + } + return; + } + + if (grantResults.length == 0) { + callback.error("No permissions not granted."); + return; + } + //Android 12 (API 31) and higher // Users MUST accept BLUETOOTH_SCAN and BLUETOOTH_CONNECT // Android 10 (API 29) up to Android 11 (API 30) @@ -1258,22 +1272,21 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, int // Android 9 (API 28) and lower // Users MUST accept ACCESS_COARSE_LOCATION for (int i = 0; i < permissions.length; i++) { - if (permissions[i].equals(Manifest.permission.ACCESS_FINE_LOCATION) && grantResults[i] == PackageManager.PERMISSION_DENIED) { LOG.d(TAG, "User *rejected* Fine Location Access"); - this.permissionCallback.error("Location permission not granted."); + callback.error("Location permission not granted."); return; } else if (permissions[i].equals(Manifest.permission.ACCESS_COARSE_LOCATION) && grantResults[i] == PackageManager.PERMISSION_DENIED) { LOG.d(TAG, "User *rejected* Coarse Location Access"); - this.permissionCallback.error("Location permission not granted."); + callback.error("Location permission not granted."); return; } else if (permissions[i].equals(BLUETOOTH_SCAN) && grantResults[i] == PackageManager.PERMISSION_DENIED) { LOG.d(TAG, "User *rejected* Bluetooth_Scan Access"); - this.permissionCallback.error("Bluetooth scan permission not granted."); + callback.error("Bluetooth scan permission not granted."); return; } else if (permissions[i].equals(BLUETOOTH_CONNECT) && grantResults[i] == PackageManager.PERMISSION_DENIED) { LOG.d(TAG, "User *rejected* Bluetooth_Connect Access"); - this.permissionCallback.error("Bluetooth Connect permission not granted."); + callback.error("Bluetooth Connect permission not granted."); return; } } @@ -1281,14 +1294,12 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, int switch(requestCode) { case REQUEST_ENABLE_BLUETOOTH: LOG.d(TAG, "User granted Bluetooth Connect access for enable bluetooth"); - enableBluetooth(permissionCallback); - this.permissionCallback = null; + enableBluetooth(callback); break; case REQUEST_BLUETOOTH_SCAN: LOG.d(TAG, "User granted Bluetooth Scan Access"); - findLowEnergyDevices(permissionCallback, serviceUUIDs, scanSeconds, scanSettings); - this.permissionCallback = null; + findLowEnergyDevices(callback, serviceUUIDs, scanSeconds, scanSettings); this.serviceUUIDs = null; this.scanSeconds = -1; this.scanSettings = null; @@ -1296,32 +1307,34 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, int case REQUEST_BLUETOOTH_CONNECT: LOG.d(TAG, "User granted Bluetooth Connect Access"); - connect(permissionCallback, deviceMacAddress); - this.permissionCallback = null; + connect(callback, deviceMacAddress); this.deviceMacAddress = null; break; case REQUEST_BLUETOOTH_CONNECT_AUTO: LOG.d(TAG, "User granted Bluetooth Auto Connect Access"); - autoConnect(permissionCallback, deviceMacAddress); - this.permissionCallback = null; + autoConnect(callback, deviceMacAddress); this.deviceMacAddress = null; break; case REQUEST_GET_BONDED_DEVICES: LOG.d(TAG, "User granted permissions for bonded devices"); - getBondedDevices(permissionCallback); - this.permissionCallback = null; + getBondedDevices(callback); break; case REQUEST_LIST_KNOWN_DEVICES: LOG.d(TAG, "User granted permissions for list known devices"); - listKnownDevices(permissionCallback); - this.permissionCallback = null; + listKnownDevices(callback); break; } } + private CallbackContext popPermissionsCallback() { + final CallbackContext callback = this.permissionCallback; + this.permissionCallback = null; + return callback; + } + private UUID uuidFromString(String uuid) { return UUIDHelper.uuidFromString(uuid); } From 216fe2e692b4a5f11f6a1676cb016bcd79de2275 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 18 Oct 2022 17:50:38 +1100 Subject: [PATCH 12/86] Restore refreshCallback override behaviour #936 --- src/android/Peripheral.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/android/Peripheral.java b/src/android/Peripheral.java index de18277a..2ad50436 100644 --- a/src/android/Peripheral.java +++ b/src/android/Peripheral.java @@ -384,9 +384,7 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (refreshCallback != null) { refreshCallback.sendPluginResult(result); refreshCallback = null; - } - - if (connectCallback != null) { + } else if (connectCallback != null) { connectCallback.sendPluginResult(result); } } else { From 299d1db76af9deb18f122f549d0775df4dd750a0 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 18 Oct 2022 17:53:36 +1100 Subject: [PATCH 13/86] Clear refreshCallback when a new connect attempt is made This ensures the refresh callback can't get stuck permanently preventing the connect callback from firing. --- src/android/Peripheral.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/android/Peripheral.java b/src/android/Peripheral.java index 2ad50436..34520183 100644 --- a/src/android/Peripheral.java +++ b/src/android/Peripheral.java @@ -105,6 +105,10 @@ public void connect(CallbackContext callbackContext, Activity activity, boolean currentActivity = activity; autoconnect = auto; connectCallback = callbackContext; + if (refreshCallback != null) { + refreshCallback.error(this.asJSONObject("refreshDeviceCache aborted due to new connect call")); + refreshCallback = null; + } gattConnect(); From 21286c3f8f620c234d3ed7362b827b8a95ec20b7 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 19 Oct 2022 00:37:01 +1100 Subject: [PATCH 14/86] 1.6.0 --- CHANGES.txt | 9 +++++++++ package.json | 2 +- plugin.xml | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f0ced4a7..e37dbbe3 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,12 @@ += 1.6.0 = +Android: Clear up time-based stopScan when new scan is started #902 +Android: Report if Bluetooth is disabled when scanning/connecting #826 +Android: Restore refreshDeviceCache to 1.3.X functionality (related to #936) +Android: Prevent various null ref exceptions (#936, #901 #871, #773, #698) +Android: Document advanced android scan options +Android: Improve Android 11 background permissions request ordering +iOS: Make connect failure errors on iOS more concise #933 Thanks doug-a-brunner + = 1.5.2 = Android: Exclude ACCESS_BACKGROUND_LOCATION from plugin.xml to avoid complications with Capacitor #928 diff --git a/package.json b/package.json index 1fc47390..dc656f81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.5.2", + "version": "1.6.0", "description": "Bluetooth Low Energy (BLE) Central Plugin", "cordova": { "id": "cordova-plugin-ble-central", diff --git a/plugin.xml b/plugin.xml index e55a992b..36fa29f0 100644 --- a/plugin.xml +++ b/plugin.xml @@ -3,7 +3,7 @@ xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-ble-central" - version="1.5.2"> + version="1.6.0"> BLE Bluetooth Low Energy (BLE) Central Plugin From efab106d79b3eb7dfd87f1bc3eae01b444462947 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 19 Oct 2022 00:46:34 +1100 Subject: [PATCH 15/86] Add missing change note --- CHANGES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.txt b/CHANGES.txt index e37dbbe3..38bf8a15 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ = 1.6.0 = +Android: Fix enable bluetooth permissions on Android 12+ #940 #941 Thanks samvimes01 Android: Clear up time-based stopScan when new scan is started #902 Android: Report if Bluetooth is disabled when scanning/connecting #826 Android: Restore refreshDeviceCache to 1.3.X functionality (related to #936) From 8da824cffb87249570851a3453e185de154872a6 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 8 Nov 2022 21:45:08 +1100 Subject: [PATCH 16/86] Add documentation for a `-slim` variant of the plugin --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 5d91087a..b7bf902f 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ The plugin provides a simple [JavaScript API](#api) for iOS and Android. - Read the value of a characteristic - Write new value to a characteristic - Get notified when characteristic's value changes +- Transfer data via L2CAP channels Advertising information is returned when scanning for peripherals. Service, characteristic, and property info is returned when connecting to a peripheral. @@ -61,6 +62,21 @@ For more information about background operation, see [Background Scanning and No ### Android +If you are having Android permissions conflicts with other plugins, try using the `slim` variant of the plugin instead with `cordova plugin add cordova-plugin-ble-central@slim`. This variant excludes all Android permissions, leaving it to the developer to ensure the right entries are added manually to the `AndroidManifest.xml` (see below for an example). + +To include the default set of permissions the plugin installs on Android SDK v31+, add the following snippet in your `config.xml` file, in the `` section: + +``` + + + + + + + + +``` + If your app targets Android 10 (API level 29) or higher, you may need the ACCESS_BACKGROUND_LOCATION permission on Android 10 & Android 11 in order for scanning to function when your app is not visible. To enable this permission and feature, set `ACCESS_BACKGROUND_LOCATION ` to true when installing: --variable ACCESS_BACKGROUND_LOCATION=true @@ -1337,6 +1353,12 @@ Run the app on your phone 3. Update release notes 4. `npm publish --registry=https://registry.npmjs.org` +## Release (lean) + +1. `git merge master` +2. Align `package.json` and `plugin.xml` versions +3. `npm publish --tag lean --registry=https://registry.npmjs.org` + # Nordic DFU If you need Nordic DFU capability, Tomáš Bedřich has a [fork](https://github.com/fxe-gear/cordova-plugin-ble-central) of this plugin that adds an `updateFirmware()` method that allows users to upgrade nRF5x based chips over the air. https://github.com/fxe-gear/cordova-plugin-ble-central From 7e56554fcacbb5bedf2bc9565d99fa866b920b6e Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 8 Nov 2022 22:10:43 +1100 Subject: [PATCH 17/86] Add registry to config --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index dc656f81..c20101c5 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,9 @@ "wp8" ] }, + "publishConfig": { + "registry": "https://registry.npmjs.org" + }, "repository": { "type": "git", "url": "git+https://github.com/don/cordova-plugin-ble-central.git" From dd7f491c30aedc9622e932da3c01c893e5165a94 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 8 Nov 2022 22:10:06 +1100 Subject: [PATCH 18/86] 1.6.1 --- CHANGES.txt | 3 +++ package.json | 2 +- plugin.xml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 38bf8a15..df3762c4 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ += 1.6.1 = +No changes - republish only + = 1.6.0 = Android: Fix enable bluetooth permissions on Android 12+ #940 #941 Thanks samvimes01 Android: Clear up time-based stopScan when new scan is started #902 diff --git a/package.json b/package.json index c20101c5..ed85324a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.6.0", + "version": "1.6.1", "description": "Bluetooth Low Energy (BLE) Central Plugin", "cordova": { "id": "cordova-plugin-ble-central", diff --git a/plugin.xml b/plugin.xml index 36fa29f0..2e2c1ba9 100644 --- a/plugin.xml +++ b/plugin.xml @@ -3,7 +3,7 @@ xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-ble-central" - version="1.6.0"> + version="1.6.1"> BLE Bluetooth Low Energy (BLE) Central Plugin From 5117f0868c780b6d1987646c68613bdf32e62e13 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Thu, 22 Dec 2022 21:00:37 +1100 Subject: [PATCH 19/86] Remove obsolete cordova wp8 platform --- package.json | 3 +- plugin.xml | 15 ----- src/wp/BLECentralPlugin.cs | 132 ------------------------------------- 3 files changed, 1 insertion(+), 149 deletions(-) delete mode 100644 src/wp/BLECentralPlugin.cs diff --git a/package.json b/package.json index ed85324a..a9fe6e7a 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,7 @@ "platforms": [ "ios", "android", - "browser", - "wp8" + "browser" ] }, "publishConfig": { diff --git a/plugin.xml b/plugin.xml index 2e2c1ba9..24839a39 100644 --- a/plugin.xml +++ b/plugin.xml @@ -101,19 +101,4 @@ - - - - - - - - - - - - - - - diff --git a/src/wp/BLECentralPlugin.cs b/src/wp/BLECentralPlugin.cs deleted file mode 100644 index da4f4904..00000000 --- a/src/wp/BLECentralPlugin.cs +++ /dev/null @@ -1,132 +0,0 @@ -// -// BLE Central Cordova Plugin -// -// (c) 2105 Don Coleman -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.Linq; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.Serialization; -using Windows.Networking.Proximity; -using WPCordovaClassLib.Cordova; -using WPCordovaClassLib.Cordova.Commands; -using WPCordovaClassLib.Cordova.JSON; -using Microsoft.Phone.Tasks; - -using Windows.Networking; -using System.Text; -using System.Threading; - -public class BLECentralPlugin : BaseCommand -{ - - private void notImplemented() - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, "Not Implemented")); - } - - public void scan(string args) - { - notImplemented(); - } - public void startScan(string args) - { - notImplemented(); - } - public void stopScan(string args) - { - notImplemented(); - } - public void startScanWithOptions(string args) - { - notImplemented(); - } - public void connect(string args) - { - notImplemented(); - } - public void disconnect(string args) - { - notImplemented(); - } - public void read(string args) - { - notImplemented(); - } - public void readRSSI(string args) - { - notImplemented(); - } - public void write(string args) - { - notImplemented(); - } - public void writeWithoutResponse(string args) - { - notImplemented(); - } - public void startNotification(string args) - { - notImplemented(); - } - public void stopNotification(string args) - { - notImplemented(); - } - public void isConnected(string args) - { - notImplemented(); - } - - public async void isEnabled(string args) - { - string callbackId = JsonHelper.Deserialize(args)[0]; - - // This is a bad way to do this, improve later - // See if we can determine in the Connection Manager - // https://msdn.microsoft.com/library/windows/apps/jj207007(v=vs.105).aspx - PeerFinder.AlternateIdentities["Bluetooth:Paired"] = ""; - - try - { - var peers = await PeerFinder.FindAllPeersAsync(); - - // Handle the result of the FindAllPeersAsync call - } - catch (Exception ex) - { - if ((uint)ex.HResult == 0x8007048F) - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR), callbackId); - } - else - { - DispatchCommandResult(new PluginResult(PluginResult.Status.ERROR, ex.Message), callbackId); - } - } - - DispatchCommandResult(new PluginResult(PluginResult.Status.OK), callbackId); - } - - public void showBluetoothSettings(string args) - { - ConnectionSettingsTask connectionSettingsTask = new ConnectionSettingsTask(); - connectionSettingsTask.ConnectionSettingsType = ConnectionSettingsType.Bluetooth; - connectionSettingsTask.Show(); - DispatchCommandResult(new PluginResult(PluginResult.Status.OK)); - } - -} From e9d17db5c50c5b8d2e04f3471522407ccf686f3b Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Thu, 22 Dec 2022 21:03:52 +1100 Subject: [PATCH 20/86] Include capacitor in keywords list --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a9fe6e7a..58a659fa 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "keywords": [ "cordova", + "capacitor", "bluetooth", "ble", "bluetoothle", From b342867eda67f901fadc89f3df3736c7bd1a0ae4 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 15 Feb 2023 12:12:38 +1100 Subject: [PATCH 21/86] Android: More thoroughly clean up callbacks on device disconnect #954 Fixes #954 --- src/android/BLECentralPlugin.java | 2 +- src/android/Peripheral.java | 35 +++++++++++++++++-------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 36003635..03d99959 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -786,7 +786,7 @@ private void disconnect(CallbackContext callbackContext, String macAddress) { private void queueCleanup(CallbackContext callbackContext, String macAddress) { Peripheral peripheral = peripherals.get(macAddress); if (peripheral != null) { - peripheral.queueCleanup(); + peripheral.queueCleanup("Aborted due to queue cleanup"); } callbackContext.success(); } diff --git a/src/android/Peripheral.java b/src/android/Peripheral.java index 34520183..6ae883c5 100644 --- a/src/android/Peripheral.java +++ b/src/android/Peripheral.java @@ -89,8 +89,8 @@ private void gattConnect() { closeGatt(); connected = false; connecting = true; - queueCleanup(); - callbackCleanup(); + queueCleanup("Aborted by new connect call"); + callbackCleanup("Aborted by new connect call"); BluetoothDevice device = getDevice(); if (Build.VERSION.SDK_INT < 23) { @@ -105,11 +105,6 @@ public void connect(CallbackContext callbackContext, Activity activity, boolean currentActivity = activity; autoconnect = auto; connectCallback = callbackContext; - if (refreshCallback != null) { - refreshCallback.error(this.asJSONObject("refreshDeviceCache aborted due to new connect call")); - refreshCallback = null; - } - gattConnect(); PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT); @@ -125,8 +120,8 @@ public void disconnect() { autoconnect = false; closeGatt(); - queueCleanup(); - callbackCleanup(); + queueCleanup("Central disconnected"); + callbackCleanup("Central disconnected"); } // the peripheral disconnected @@ -142,8 +137,8 @@ public void peripheralDisconnected(String message) { sendDisconnectMessage(message); - queueCleanup(); - callbackCleanup(); + queueCleanup(message); + callbackCleanup(message); } private void closeGatt() { @@ -852,10 +847,10 @@ public void queueReadRSSI(CallbackContext callbackContext) { queueCommand(command); } - public void queueCleanup() { + public void queueCleanup(String message) { bleProcessing.set(true); // Stop anything else trying to process for (BLECommand command = commandQueue.poll(); command != null; command = commandQueue.poll()) { - command.getCallbackContext().error("Peripheral Disconnected"); + command.getCallbackContext().error(message); } bleProcessing.set(false); // Now re-allow processing @@ -873,18 +868,26 @@ public void writeL2CapChannel(CallbackContext callbackContext, int psm, byte[] d getOrAddL2CAPContext(psm).writeL2CapChannel(callbackContext, data); } - private void callbackCleanup() { + private void callbackCleanup(String message) { synchronized(this) { if (readCallback != null) { - readCallback.error(this.asJSONObject("Peripheral Disconnected")); + readCallback.error(this.asJSONObject(message)); readCallback = null; commandCompleted(); } if (writeCallback != null) { - writeCallback.error(this.asJSONObject("Peripheral Disconnected")); + writeCallback.error(this.asJSONObject(message)); writeCallback = null; commandCompleted(); } + if (refreshCallback != null) { + refreshCallback.error(this.asJSONObject(message)); + refreshCallback = null; + } + if (requestMtuCallback != null) { + requestMtuCallback.error(message); + requestMtuCallback = null; + } } } From 7b8ad1c57adffbb0ad9a6cb16b66777c6eb277a6 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 15 Feb 2023 12:28:17 +1100 Subject: [PATCH 22/86] Android: Don't disconnect if refreshDeviceCache fails It's not immediately obvious that what the right behaviour here is if service discovery fails, but if the connection has been established, it seems safe to continue leaving it open. Reverts 367f900ba5c49e4c405eef0348dcf6e946c96f37 --- src/android/Peripheral.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/android/Peripheral.java b/src/android/Peripheral.java index 6ae883c5..ad3e9d66 100644 --- a/src/android/Peripheral.java +++ b/src/android/Peripheral.java @@ -391,9 +391,9 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (refreshCallback != null) { refreshCallback.error(this.asJSONObject("Service discovery failed")); refreshCallback = null; + } else { + peripheralDisconnected("Service discovery failed"); } - - peripheralDisconnected("Service discovery failed"); } } From d6751a55188ca7ee3d6941b25115f46e7522dd9d Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 15 Feb 2023 12:53:10 +1100 Subject: [PATCH 23/86] Android: Don't leak refreshDeviceCache callback --- src/android/Peripheral.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/android/Peripheral.java b/src/android/Peripheral.java index ad3e9d66..5bdaf261 100644 --- a/src/android/Peripheral.java +++ b/src/android/Peripheral.java @@ -379,11 +379,11 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { PluginResult result = new PluginResult(PluginResult.Status.OK, this.asJSONObject(gatt)); - result.setKeepCallback(true); if (refreshCallback != null) { refreshCallback.sendPluginResult(result); refreshCallback = null; } else if (connectCallback != null) { + result.setKeepCallback(true); connectCallback.sendPluginResult(result); } } else { From 18400f5639fab0ceff4cb72619a217ff48437243 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 15 Feb 2023 16:50:59 +1100 Subject: [PATCH 24/86] 1.6.2 --- CHANGES.txt | 5 +++++ package.json | 2 +- plugin.xml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index df3762c4..3bc8ce09 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,8 @@ += 1.6.2 = +Android: Don't leak refreshDeviceCache callback +Android: More thoroughly clean up callbacks on device disconnect #954 +Android: Don't disconnect if refreshDeviceCache fails + = 1.6.1 = No changes - republish only diff --git a/package.json b/package.json index 58a659fa..f2c522c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.6.1", + "version": "1.6.2", "description": "Bluetooth Low Energy (BLE) Central Plugin", "cordova": { "id": "cordova-plugin-ble-central", diff --git a/plugin.xml b/plugin.xml index 24839a39..b046ddee 100644 --- a/plugin.xml +++ b/plugin.xml @@ -3,7 +3,7 @@ xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-ble-central" - version="1.6.1"> + version="1.6.2"> BLE Bluetooth Low Energy (BLE) Central Plugin From a3c6265a75c728e000dabbe3562c3332987acca1 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Fri, 9 Apr 2021 16:51:41 +1000 Subject: [PATCH 25/86] Implement manual bond control on Android (#605) --- README.md | 80 +++++++++++++++++++++++ src/android/BLECentralPlugin.java | 101 ++++++++++++++++++++++++++++++ src/android/Peripheral.java | 84 +++++++++++++++++++++++++ src/browser/BLECentralPlugin.js | 8 ++- types.d.ts | 41 ++++++++++++ www/ble.js | 30 +++++++++ 6 files changed, 343 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b7bf902f..6b27c652 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,9 @@ For the best understanding about which permissions are needed for which combinat - [ble.startScanWithOptions](#startscanwithoptions) - [ble.stopScan](#stopscan) - [ble.setPin](#setpin) +- [ble.bond](#bond) +- [ble.unbond](#unbond) +- [ble.readBondState](#readbondstate) - [ble.connect](#connect) - [ble.autoConnect](#autoconnect) - [ble.disconnect](#disconnect) @@ -312,6 +315,83 @@ Function `setPin` sets the pin when device requires it. - **success**: Success callback function that is invoked when the function is invoked. [optional] - **failure**: Error callback function, invoked when error occurs. [optional] +## bond + +Initiate a bond with a remote device + + ble.bond(device_id, [success], [failure], [options]); + // Or using await with promises + await ble.withPromises.bond(device_id); + await ble.withPromises.bond(device_id, { usePairingDialog: true }); + +### Description + +Function `bond` initialises a bond with a peripheral. The success callback will be called when the +bonding is complete. The bonding process may prompt the user to confirm the pairing process. + +### Parameters + +- **device_id**: UUID or MAC address of the peripheral +- **success**: Success callback function that is invoked when the bonding succeeds. [optional] +- **failure**: Error callback function, invoked when the bonding fails. [optional] +- **options**: an object specifying a set of name-value pairs. The currently acceptable options are: + - _usePairingDialog_: _true_ (default) Show pairing request as a dialog rather than a notification [optional] + +### Supported Platforms + +- Android + +## unbond + +Remove a bond for a remote device + + ble.unbond(device_id, [success], [failure]); + // Or using await with promises + await ble.withPromises.unbond(device_id); + +### Description + +Function `unbond` removes an existing bond for a remote device. The success callback will be called when the +bond has been removed. + +### Parameters + +- **device_id**: UUID or MAC address of the peripheral +- **success**: Success callback function that is invoked when the bond is removed. [optional] +- **failure**: Error callback function, invoked when the bond removal fails. [optional] + +### Supported Platforms + +- Android + +## readBondState + +Get the bond state of a device as a string + + ble.readBondState(device_id, [success], [failure]); + // Or using await with promises + const bondState = await ble.withPromises.readBondState(device_id); + +### Description + +Function `readBondState` retrieves the current bond state of the device. + +**States** + +- "none" +- "bonding" +- "bonded" + +### Parameters + +- **device_id**: UUID or MAC address of the peripheral +- **success**: Success callback function that is invoked with the current bond state. [optional] +- **failure**: Error callback function, invoked when error occurs. [optional] + +### Supported Platforms + +- Android + ## connect Connect to a peripheral. diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 03d99959..563dca21 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -54,6 +54,8 @@ import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_DUAL; import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE; +import static android.bluetooth.BluetoothDevice.ACTION_BOND_STATE_CHANGED; +import static android.bluetooth.BluetoothDevice.EXTRA_BOND_STATE; public class BLECentralPlugin extends CordovaPlugin { // permissions @@ -75,6 +77,9 @@ public class BLECentralPlugin extends CordovaPlugin { private static final String QUEUE_CLEANUP = "queueCleanup"; private static final String SET_PIN = "setPin"; + private static final String BOND = "bond"; + private static final String UNBOND = "unbond"; + private static final String READ_BOND_STATE = "readBondState"; private static final String REQUEST_MTU = "requestMtu"; private static final String REQUEST_CONNECTION_PRIORITY = "requestConnectionPriority"; @@ -141,6 +146,8 @@ public class BLECentralPlugin extends CordovaPlugin { // Bluetooth state notification CallbackContext stateCallback; BroadcastReceiver stateReceiver; + private BroadcastReceiver bondStateReceiver; + Map bluetoothStates = new Hashtable() {{ put(BluetoothAdapter.STATE_OFF, "off"); put(BluetoothAdapter.STATE_TURNING_OFF, "turningOff"); @@ -163,6 +170,7 @@ protected void pluginInitialize() { public void onDestroy() { removeStateListener(); removeLocationStateListener(); + removeBondStateListener(); for(Peripheral peripheral : peripherals.values()) { peripheral.disconnect(); } @@ -172,6 +180,7 @@ public void onDestroy() { public void onReset() { removeStateListener(); removeLocationStateListener(); + removeBondStateListener(); for(Peripheral peripheral : peripherals.values()) { peripheral.disconnect(); } @@ -244,6 +253,24 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback String pin = args.getString(0); setPin(callbackContext, pin); + } else if (action.equals(BOND)) { + + String macAddress = args.getString(0); + JSONObject options = args.getJSONObject(1); + + boolean usePairingDialog = options != null && options.optBoolean("usePairingDialog", true); + bond(callbackContext, macAddress, usePairingDialog); + + } else if (action.equals(UNBOND)) { + + String macAddress = args.getString(0); + unbond(callbackContext, macAddress); + + } else if (action.equals(READ_BOND_STATE)) { + + String macAddress = args.getString(0); + readBondState(callbackContext, macAddress); + } else if (action.equals(REQUEST_MTU)) { String macAddress = args.getString(0); @@ -827,6 +854,52 @@ public void onReceive(Context context, Intent intent) { } } + private void bond(CallbackContext callbackContext, String macAddress, boolean usePairingDialog) { + if (!peripherals.containsKey(macAddress) && BluetoothAdapter.checkBluetoothAddress(macAddress)) { + BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress); + Peripheral peripheral = new Peripheral(device); + peripherals.put(macAddress, peripheral); + } + + Peripheral peripheral = peripherals.get(macAddress); + if (peripheral != null) { + addBondStateListener(); + peripheral.bond(callbackContext, bluetoothAdapter, usePairingDialog); + } else { + callbackContext.error("Peripheral " + macAddress + " not found."); + } + } + + private void unbond(CallbackContext callbackContext, String macAddress) { + if (!peripherals.containsKey(macAddress) && BluetoothAdapter.checkBluetoothAddress(macAddress)) { + BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress); + Peripheral peripheral = new Peripheral(device); + peripherals.put(macAddress, peripheral); + } + + Peripheral peripheral = peripherals.get(macAddress); + if (peripheral != null) { + peripheral.unbond(callbackContext); + } else { + callbackContext.success(); + } + } + + private void readBondState(CallbackContext callbackContext, String macAddress) { + if (!peripherals.containsKey(macAddress) && BluetoothAdapter.checkBluetoothAddress(macAddress)) { + BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress); + Peripheral peripheral = new Peripheral(device); + peripherals.put(macAddress, peripheral); + } + + Peripheral peripheral = peripherals.get(macAddress); + if (peripheral != null) { + peripheral.readBondState(callbackContext); + } else { + callbackContext.error("Peripheral " + macAddress + " not found."); + } + } + private void requestMtu(CallbackContext callbackContext, String macAddress, int mtuValue) { Peripheral peripheral = peripherals.get(macAddress); if (peripheral != null) { @@ -1346,4 +1419,32 @@ private void resetScanOptions() { this.reportDuplicates = false; } + private void addBondStateListener() { + if (bondStateReceiver == null) { + bondStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (ACTION_BOND_STATE_CHANGED.equals(action)) { + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + Peripheral peripheral = peripherals.get(device.getAddress()); + + if (peripheral != null) { + int bondState = intent.getIntExtra(EXTRA_BOND_STATE, BluetoothDevice.ERROR); + int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); + peripheral.updateBondState(bondState, previousBondState); + } + } + } + }; + webView.getContext().registerReceiver(bondStateReceiver, new IntentFilter(ACTION_BOND_STATE_CHANGED)); + } + } + + private void removeBondStateListener() { + if (bondStateReceiver != null) { + webView.getContext().unregisterReceiver(bondStateReceiver); + bondStateReceiver = null; + } + } } diff --git a/src/android/Peripheral.java b/src/android/Peripheral.java index 5bdaf261..bb1c64a5 100644 --- a/src/android/Peripheral.java +++ b/src/android/Peripheral.java @@ -42,6 +42,11 @@ public class Peripheral extends BluetoothGattCallback { //public final static UUID CLIENT_CHARACTERISTIC_CONFIGURATION_UUID = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB"); public final static UUID CLIENT_CHARACTERISTIC_CONFIGURATION_UUID = UUIDHelper.uuidFromString("2902"); private static final String TAG = "Peripheral"; + private final static Map bondStates = new Hashtable() {{ + put(BluetoothDevice.BOND_NONE, "none"); + put(BluetoothDevice.BOND_BONDING, "bonding"); + put(BluetoothDevice.BOND_BONDED, "bonded"); + }}; private static final int FAKE_PERIPHERAL_RSSI = 0x7FFFFFFF; @@ -62,6 +67,7 @@ public class Peripheral extends BluetoothGattCallback { private CallbackContext readCallback; private CallbackContext writeCallback; private CallbackContext requestMtuCallback; + private CallbackContext bondStateCallback; private Activity currentActivity; private Map notificationCallbacks = new HashMap(); @@ -989,4 +995,82 @@ private L2CAPContext getOrAddL2CAPContext(int psm) { return context; } } + + public void bond(CallbackContext callbackContext, BluetoothAdapter bluetoothAdapter, boolean usePairingDialog) { + if (bondStateCallback != null) { + bondStateCallback.error("Aborted by new bond call"); + bondStateCallback = null; + } + + int bondState = device.getBondState(); + if (bondState == BluetoothDevice.BOND_BONDED) { + callbackContext.success(); + return; + } + + bondStateCallback = callbackContext; + if (bondState == BluetoothDevice.BOND_NONE) { + if (usePairingDialog) { + // Fake Bluetooth discovery so Android gives us a prompt rather than a crappy notification + // Source: https://stackoverflow.com/a/59881926 + bluetoothAdapter.startDiscovery(); + bluetoothAdapter.cancelDiscovery(); + } + if (!device.createBond()) { + bondStateCallback = null; + callbackContext.error("createBond returned false"); + } + } + } + + public void unbond(CallbackContext callbackContext) { + final int bondState = device.getBondState(); + if (bondState == BluetoothDevice.BOND_NONE) { + callbackContext.success(); + return; + } + + if (bondState != BluetoothDevice.BOND_BONDED) { + LOG.w(TAG, "Unbonding device in state %s", bondState); + } + + try { + //noinspection JavaReflectionMemberAccess + final Method removeBond = device.getClass().getMethod("removeBond"); + if (removeBond == null) { + LOG.w(TAG, "removeBond method not found on gatt"); + callbackContext.error("removeBond method not found on gatt"); + return; + } + + if(removeBond.invoke(device) != Boolean.TRUE) { + LOG.w(TAG, "removeBond returned false"); + callbackContext.error("removeBond returned false"); + return; + } + + callbackContext.success(); + } catch (final Exception e) { + LOG.w(TAG, "removeBond threw an exception", e); + callbackContext.error("removeBond threw an exception: " + e.getMessage()); + } + } + + public void updateBondState(int bondState, int previousBondState) { + LOG.d(TAG, "Bonding state update %s => %s", previousBondState, bondState); + if (bondStateCallback == null) return; + + if (bondState == BluetoothDevice.BOND_BONDED || bondState == BluetoothDevice.BOND_NONE) { + if (bondState == BluetoothDevice.BOND_BONDED) { + bondStateCallback.success(); + } else { + bondStateCallback.error("Unsuccessful bond state: " + bondStates.get(bondState)); + } + bondStateCallback = null; + } + } + + public void readBondState(CallbackContext callbackContext) { + callbackContext.success(bondStates.get(device.getBondState())); + } } diff --git a/src/browser/BLECentralPlugin.js b/src/browser/BLECentralPlugin.js index ade8fe11..76ee6dcf 100644 --- a/src/browser/BLECentralPlugin.js +++ b/src/browser/BLECentralPlugin.js @@ -200,5 +200,11 @@ module.exports = { }, stopStateNotifications: function(success, failure) { notSupported(failure); - } + }, + bond: function (success, failure) { + notSupported(failure); + }, + readBondState: function (success, failure) { + notSupported(failure); + }, }; diff --git a/types.d.ts b/types.d.ts index adb48021..318667db 100644 --- a/types.d.ts +++ b/types.d.ts @@ -1,5 +1,6 @@ declare namespace BLECentralPlugin { type PeripheralState = 'disconnected' | 'disconnecting' | 'connecting' | 'connected'; + type BondState = 'none' | 'bonding' | 'bonded'; interface PeripheralCharacteristic { service: string; @@ -51,6 +52,11 @@ declare namespace BLECentralPlugin { secureChannel?: boolean; } + interface CreateBondOptions { + /* Show pairing request as a dialog rather than a notification */ + usePairingDialog: boolean; + } + interface L2CAP { close(device_id: string, psm: number, success?: () => any, failure?: (error: string | BLEError) => any): void; @@ -177,6 +183,21 @@ declare namespace BLECentralPlugin { requestConnectionPriority(device_id: string, priority: 'high' | 'balanced' | 'low'): Promise; restoredBluetoothState(): Promise; + + /* Start the bonding (pairing) process with the remote device. + [iOS] bond is not supported on iOS. + */ + bond(device_id: string, options?: CreateBondOptions): Promise; + + /* unbonds a remote device. Note: this uses an unlisted API on Android. + [iOS] bond is not supported on iOS. + */ + unbond(device_id: string): Promise; + + /* Read the bond state of the remote device. + [iOS] readBondState is not supported on iOS. + */ + readBondState(device_id: string): Promise; } export interface BLECentralPluginStatic extends BLECentralPluginCommon { @@ -348,6 +369,26 @@ declare namespace BLECentralPlugin { restoredBluetoothState(success: (data: RestoredState) => any, failure?: (error: string) => any): void; withPromises: BLECentralPluginPromises; + + /* Start the bonding (pairing) process with the remote device. + [iOS] bond is not supported on iOS. + */ + bond( + device_id: string, + success: () => any, + failure?: (error: string) => any, + options?: CreateBondOptions + ): void; + + /* unbonds a remote device. Note: this uses an unlisted API on Android. + [iOS] bond is not supported on iOS. + */ + unbond(device_id: string, success: () => any, failure?: (error: string) => any): void; + + /* Read the bond state of the remote device. + [iOS] readBondState is not supported on iOS. + */ + readBondState(device_id: string, success: (state: BondState) => any, failure?: (error: string) => any): void; } } diff --git a/www/ble.js b/www/ble.js index 9888eb59..87ba597b 100644 --- a/www/ble.js +++ b/www/ble.js @@ -270,6 +270,18 @@ module.exports = { restoredBluetoothState: function (success, failure) { cordova.exec(success, failure, 'BLE', 'restoredBluetoothState', []); }, + + bond: function (device_id, success, failure, options) { + cordova.exec(success, failure, 'BLE', 'bond', [device_id, options || {}]); + }, + + unbond: function (device_id, success, failure) { + cordova.exec(success, failure, 'BLE', 'unbond', [device_id]); + }, + + readBondState: function (device_id, success, failure) { + cordova.exec(success, failure, 'BLE', 'readBondState', [device_id]); + }, }; module.exports.withPromises = { @@ -441,6 +453,24 @@ module.exports.withPromises = { module.exports.restoredBluetoothState(resolve, reject); }); }, + + bond: function (device_id, options) { + return new Promise(function (resolve, reject) { + module.exports.bond(device_id, resolve, reject, options); + }); + }, + + unbond: function (device_id) { + return new Promise(function (resolve, reject) { + module.exports.unbond(device_id, resolve, reject); + }); + }, + + readBondState: function (device_id) { + return new Promise(function (resolve, reject) { + module.exports.readBondState(device_id, resolve, reject); + }); + }, }; module.exports.l2cap = { From 40dc99e1982e497b2286bb34e916074047ce8908 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 18 Feb 2023 14:26:37 +1100 Subject: [PATCH 26/86] Remove isDiscovering check This was added for Android 6, but it's unclear how this might affect device scanning. Docos suggest this only impacts connecting, so should be safe to ignore. --- src/android/BLECentralPlugin.java | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 563dca21..60dacb13 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -1211,13 +1211,6 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic return; } - // return error if already scanning - if (bluetoothAdapter.isDiscovering()) { - LOG.w(TAG, "Tried to start scan while already running."); - callbackContext.error("Tried to start scan while already running."); - return; - } - // clear non-connected cached peripherals for(Iterator> iterator = peripherals.entrySet().iterator(); iterator.hasNext(); ) { Map.Entry entry = iterator.next(); From 27e12354f6bb9f1b6ee4509c82c22f7f08f169a8 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 18 Feb 2023 20:07:00 +1100 Subject: [PATCH 27/86] Request correct permissions on Android 12+ --- src/android/BLECentralPlugin.java | 58 +++++++++++++++++++++++++++++++ src/android/Peripheral.java | 4 +++ 2 files changed, 62 insertions(+) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 60dacb13..3bf09e65 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -135,9 +135,13 @@ public class BLECentralPlugin extends CordovaPlugin { private static final int REQUEST_BLUETOOTH_CONNECT_AUTO = 4; private static final int REQUEST_GET_BONDED_DEVICES = 5; private static final int REQUEST_LIST_KNOWN_DEVICES = 6; + private static final int REQUEST_BOND = 7; + private static final int REQUEST_UNBOND = 8; + private static final int REQUEST_READ_BOND_STATE = 9; private static int COMPILE_SDK_VERSION = -1; private CallbackContext permissionCallback; private String deviceMacAddress; + private boolean usePairingDialog; private UUID[] serviceUUIDs; private int scanSeconds; private ScanSettings scanSettings; @@ -855,6 +859,23 @@ public void onReceive(Context context, Intent intent) { } private void bond(CallbackContext callbackContext, String macAddress, boolean usePairingDialog) { + if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S + List missingPermissions = new ArrayList(); + if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) { + missingPermissions.add(BLUETOOTH_CONNECT); + } + if (usePairingDialog && !PermissionHelper.hasPermission(this, BLUETOOTH_SCAN)) { + missingPermissions.add(BLUETOOTH_SCAN); + } + if (!missingPermissions.isEmpty()) { + permissionCallback = callbackContext; + deviceMacAddress = macAddress; + this.usePairingDialog = usePairingDialog; + PermissionHelper.requestPermissions(this, REQUEST_BOND, missingPermissions.toArray(new String[0])); + return; + } + } + if (!peripherals.containsKey(macAddress) && BluetoothAdapter.checkBluetoothAddress(macAddress)) { BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress); Peripheral peripheral = new Peripheral(device); @@ -871,6 +892,15 @@ private void bond(CallbackContext callbackContext, String macAddress, boolean us } private void unbond(CallbackContext callbackContext, String macAddress) { + if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S + if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) { + permissionCallback = callbackContext; + deviceMacAddress = macAddress; + PermissionHelper.requestPermission(this, REQUEST_UNBOND, BLUETOOTH_CONNECT); + return; + } + } + if (!peripherals.containsKey(macAddress) && BluetoothAdapter.checkBluetoothAddress(macAddress)) { BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress); Peripheral peripheral = new Peripheral(device); @@ -886,6 +916,15 @@ private void unbond(CallbackContext callbackContext, String macAddress) { } private void readBondState(CallbackContext callbackContext, String macAddress) { + if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S + if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) { + permissionCallback = callbackContext; + deviceMacAddress = macAddress; + PermissionHelper.requestPermission(this, REQUEST_READ_BOND_STATE, BLUETOOTH_CONNECT); + return; + } + } + if (!peripherals.containsKey(macAddress) && BluetoothAdapter.checkBluetoothAddress(macAddress)) { BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress); Peripheral peripheral = new Peripheral(device); @@ -1392,6 +1431,25 @@ public void onRequestPermissionResult(int requestCode, String[] permissions, int LOG.d(TAG, "User granted permissions for list known devices"); listKnownDevices(callback); break; + + case REQUEST_BOND: + LOG.d(TAG, "User granted permissions for bond"); + bond(callback, deviceMacAddress, usePairingDialog); + this.deviceMacAddress = null; + this.usePairingDialog = true; + break; + + case REQUEST_UNBOND: + LOG.d(TAG, "User granted permissions for unbond"); + unbond(callback, deviceMacAddress); + this.deviceMacAddress = null; + break; + + case REQUEST_READ_BOND_STATE: + LOG.d(TAG, "User granted permissions for read bond state"); + readBondState(callback, deviceMacAddress); + this.deviceMacAddress = null; + break; } } diff --git a/src/android/Peripheral.java b/src/android/Peripheral.java index bb1c64a5..9b7b15a9 100644 --- a/src/android/Peripheral.java +++ b/src/android/Peripheral.java @@ -32,6 +32,7 @@ import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicBoolean; +import androidx.annotation.RequiresPermission; /** * Peripheral wraps the BluetoothDevice and provides methods to convert to JSON. @@ -996,6 +997,7 @@ private L2CAPContext getOrAddL2CAPContext(int psm) { } } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public void bond(CallbackContext callbackContext, BluetoothAdapter bluetoothAdapter, boolean usePairingDialog) { if (bondStateCallback != null) { bondStateCallback.error("Aborted by new bond call"); @@ -1023,6 +1025,7 @@ public void bond(CallbackContext callbackContext, BluetoothAdapter bluetoothAdap } } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public void unbond(CallbackContext callbackContext) { final int bondState = device.getBondState(); if (bondState == BluetoothDevice.BOND_NONE) { @@ -1070,6 +1073,7 @@ public void updateBondState(int bondState, int previousBondState) { } } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public void readBondState(CallbackContext callbackContext) { callbackContext.success(bondStates.get(device.getBondState())); } From 42c45019d86746acfcede436603920560714d784 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Mon, 20 Feb 2023 09:46:05 +1100 Subject: [PATCH 28/86] 1.6.3 --- CHANGES.txt | 3 +++ package.json | 2 +- plugin.xml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 3bc8ce09..f3662e25 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ += 1.6.3 = +Android: Implement manual bond control on #605 #843 + = 1.6.2 = Android: Don't leak refreshDeviceCache callback Android: More thoroughly clean up callbacks on device disconnect #954 diff --git a/package.json b/package.json index f2c522c0..a326aaa9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.6.2", + "version": "1.6.3", "description": "Bluetooth Low Energy (BLE) Central Plugin", "cordova": { "id": "cordova-plugin-ble-central", diff --git a/plugin.xml b/plugin.xml index b046ddee..6b5e6747 100644 --- a/plugin.xml +++ b/plugin.xml @@ -3,7 +3,7 @@ xmlns="http://www.phonegap.com/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-ble-central" - version="1.6.2"> + version="1.6.3"> BLE Bluetooth Low Energy (BLE) Central Plugin From b894ea07b38b181a818faef6e783f44427f4b14a Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 28 Feb 2023 18:30:57 +1100 Subject: [PATCH 29/86] Detail capacitor installation instructions --- README.md | 59 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 6b27c652..1b0e8169 100644 --- a/README.md +++ b/README.md @@ -32,35 +32,58 @@ See the [examples](https://github.com/don/cordova-plugin-ble-central/tree/master # Installing -### Cordova +## Capacitor - $ cordova plugin add cordova-plugin-ble-central + $ npm install cordova-plugin-ble-central + $ npx cap sync -It's recommended to always use the latest cordova and cordova platform packages in order to enusre correct function. This plugin generally best supports the following platforms and version ranges: +The plugin can be further configured via the cordova preferences section of the [capacitor config file](https://capacitorjs.com/docs/cordova/migrating-from-cordova-to-capacitor#cordova-plugin-preferences): -| cordova | cordova-ios | cordova-android | cordova-browser | -| ------- | ----------- | --------------- | --------------- | -| 10+ | 6.2.0+ | 10.0+ | not tested | +``` +const config = { + ... + cordova: { + preferences: { + ... + bluetooth_restore_state: "true", + accessBackgroundLocation: "false", + }, + } +} +``` + +- `bluetooth_restore_state`: [**iOS**] Enable Bluetooth state restoration, allowing an app to be woken up to receive scan results and peripheral notifications. This is needed for background scanning support. See [iOS restore state](https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html#//apple_ref/doc/uid/TP40013257-CH7-SW13). For more information about background operation with this plugin, see [Background Scanning and Notifications on iOS](#background-scanning-and-notifications-on-ios). + +- `accessBackgroundLocation`: [**Android**] Tells the plugin to request the `ACCESS_BACKGROUND_LOCATION` permission on Android 10 & Android 11 in order for scanning to function when the app is not visible. ### iOS -For iOS, apps will crash unless they include usage description keys for the types of data they access. Applications targeting iOS 13 and later, define [NSBluetoothAlwaysUsageDescription](https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothalwaysusagedescription?language=objc) to tell the user why the application needs Bluetooth. For apps with a deployment target earlier than iOS 13, add [NSBluetoothPeripheralUsageDescription](https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothperipheralusagedescription?language=objc). Both of these keys can be set when installing the plugin by passing the BLUETOOTH_USAGE_DESCRIPTION variable. +After installation, the following additions should be made to the app's `Info.plist` - $ cordova plugin add cordova-plugin-ble-central --variable BLUETOOTH_USAGE_DESCRIPTION="Your description here" +- Set [NSBluetoothAlwaysUsageDescription](https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothalwaysusagedescription?language=objc) to a descriptive text, to be shown to the user on first access to the Bluetooth adapter. **If this is not defined the app will crash**. +- _(Optional)_ Add `bluetooth-central` to [UIBackgroundModes](https://developer.apple.com/documentation/bundleresources/information_property_list/uibackgroundmodes?language=objc) to enable background receipt of scan information and BLE notifications -See Apple's documentation about [Protected Resources](https://developer.apple.com/documentation/bundleresources/information_property_list/protected_resources) for more details. If your app needs other permissions like location, try the [cordova-custom-config plugin](https://github.com/don/cordova-plugin-ble-central/issues/700#issuecomment-538312656). +## Cordova -It is possible to delay the initialization of the plugin on iOS. Normally the Bluetooth permission dialog is shown when the app loads for the first time. Delaying the initialization of the plugin shows the permission dialog the first time the Bluetooth API is called. Set `IOS_INIT_ON_LOAD` to false when installing. +`$ cordova plugin add cordova-plugin-ble-central --variable BLUETOOTH_USAGE_DESCRIPTION="Your description here" --variable IOS_INIT_ON_LOAD=true|false --variable BLUETOOTH_RESTORE_STATE=true|false --variable ACCESS_BACKGROUND_LOCATION=true|false` - --variable IOS_INIT_ON_LOAD=false +It's recommended to always use the latest cordova and cordova platform packages in order to ensure correct function. This plugin generally best supports the following platforms and version ranges: -If background scanning and operation is required, the [iOS restore state](https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html#//apple_ref/doc/uid/TP40013257-CH7-SW13) should be enabled: +| cordova | cordova-ios | cordova-android | cordova-browser | +| ------- | ----------- | --------------- | --------------- | +| 10+ | 6.2.0+ | 10.0+ | not tested | - --variable BLUETOOTH_RESTORE_STATE=true +All variables can be modified after installation by updating the values in `package.json`. + +- `BLUETOOTH_USAGE_DESCRIPTION`: [**iOS**] defines the values for [NSBluetoothAlwaysUsageDescription](https://developer.apple.com/documentation/bundleresources/information_property_list/nsbluetoothalwaysusagedescription?language=objc). + +- `IOS_INIT_ON_LOAD`: [**iOS**] Prevents the Bluetooth plugin from being initialised until first access to the `ble` window object. This allows an application to warn the user before the Bluetooth access permission is requested. -For more information about background operation, see [Background Scanning and Notifications on iOS](#background-scanning-and-notifications-on-ios). +- `BLUETOOTH_RESTORE_STATE`: [**iOS**] Enable Bluetooth state restoration, allowing an app to be woken up to receive scan results and peripheral notifications. This is needed for background scanning support. See [iOS restore state](https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html#//apple_ref/doc/uid/TP40013257-CH7-SW13). For more information about background operation with this plugin, see [Background Scanning and Notifications on iOS](#background-scanning-and-notifications-on-ios). -### Android +- `ACCESS_BACKGROUND_LOCATION`: [**Android**] Tells the plugin to request the ACCESS_BACKGROUND_LOCATION permission on Android 10 & Android 11 in order for scanning to function when the app is not visible. + +## Android permission conflicts If you are having Android permissions conflicts with other plugins, try using the `slim` variant of the plugin instead with `cordova plugin add cordova-plugin-ble-central@slim`. This variant excludes all Android permissions, leaving it to the developer to ensure the right entries are added manually to the `AndroidManifest.xml` (see below for an example). @@ -77,16 +100,10 @@ To include the default set of permissions the plugin installs on Android SDK v31 ``` -If your app targets Android 10 (API level 29) or higher, you may need the ACCESS_BACKGROUND_LOCATION permission on Android 10 & Android 11 in order for scanning to function when your app is not visible. To enable this permission and feature, set `ACCESS_BACKGROUND_LOCATION ` to true when installing: - - --variable ACCESS_BACKGROUND_LOCATION=true - For the best understanding about which permissions are needed for which combinations of target SDK version & OS version, see [Android Bluetooth permissions](https://developer.android.com/guide/topics/connectivity/bluetooth/permissions) # API -## Methods - - [ble.scan](#scan) - [ble.startScan](#startscan) - [ble.startScanWithOptions](#startscanwithoptions) From 41ee114ffec38a45be5c7bcc0e0b7d4da268dc7e Mon Sep 17 00:00:00 2001 From: = Date: Thu, 4 May 2023 13:36:42 -0500 Subject: [PATCH 30/86] update readme with info for advertisement parsing module --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1b0e8169..427ea021 100644 --- a/README.md +++ b/README.md @@ -1296,7 +1296,8 @@ Bluetooth advertising data is returned in when scanning for devices. The format The advertising information for both Android and iOS appears to be a combination of advertising data and scan response data. -Ideally a common format (map or array) would be returned for both platforms in future versions. If you have ideas, please contact me. +To get consistent advertising data payloads across platforms, you can use +the [ble-central-advertisements](https://github.com/jospete/ble-central-advertisements) module. ## Android From 9c29e2159c3788f4a85edf3c7a0fa95d28e36a88 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 6 May 2023 13:52:01 +1000 Subject: [PATCH 31/86] Create GitHub Actions for Cordova (#968) --- .github/workflows/cordova.yml | 77 +++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 .github/workflows/cordova.yml diff --git a/.github/workflows/cordova.yml b/.github/workflows/cordova.yml new file mode 100644 index 00000000..edc2b09c --- /dev/null +++ b/.github/workflows/cordova.yml @@ -0,0 +1,77 @@ +name: Cordova + +on: + workflow_dispatch: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + cordova-android: + name: android@${{ matrix.platform }} cordova@${{ matrix.cordova }} + + runs-on: ubuntu-latest + + strategy: + matrix: + include: + - jdk: 16 + cordova: latest + platform: latest + node: 18.x + - jdk: 11 + cordova: 11.1.0 + platform: 10.1.2 + node: 14.x + - jdk: 8 + cordova: 10.0.0 + platform: 10.1.2 + node: 14.x + + steps: + - uses: actions/checkout@v3 + - name: set up JDK ${{ matrix.jdk }} + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.jdk }} + distribution: 'temurin' + - name: Use Node.js ${{ matrix.node }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + - name: Build test app + run: | + npm install -g cordova@${{ matrix.cordova }} + cordova create temp + cd temp + cordova platform add android@${{ matrix.platform }} + cordova plugin add .. --noregistry --force --link + cordova build android + + cordova-ios: + name: ios@${{ matrix.platform }} cordova@${{ matrix.cordova }} + + runs-on: macos-latest + + strategy: + matrix: + include: + - cordova: latest + platform: latest + node: 18.x + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + - name: Build test app + run: | + npm install -g cordova@${{ matrix.cordova }} + cordova create temp + cd temp + cordova platform add ios@${{ matrix.platform }} + cordova plugin add .. --noregistry --force --link + cordova build ios From f13186181dcb2f091c64759a17fd6406169a8ebb Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 6 May 2023 09:54:13 +1000 Subject: [PATCH 32/86] Add duration flag (in seconds) to scan with options This allows different scan modes to be easily selected while still allowing the scan to be time-limited --- README.md | 5 +++-- src/android/BLECentralPlugin.java | 3 ++- src/ios/BLECentralPlugin.m | 9 +++++++++ types.d.ts | 2 ++ 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 427ea021..c756387c 100644 --- a/README.md +++ b/README.md @@ -245,8 +245,9 @@ See the [location permission notes](#location-permission-notes) above for inform - **services**: List of services to discover, or [] to find all devices - **options**: an object specifying a set of name-value pairs. The currently acceptable options are: - - _reportDuplicates_: _true_ if duplicate devices should be reported, _false_ (default) if devices should only be reported once. [optional] - - _scanMode_: String defines [setScanMode()](https://developer.android.com/reference/kotlin/android/bluetooth/le/ScanSettings.Builder#setscanmode) argument on Android. + - _reportDuplicates_: _true_ if duplicate devices should be reported, _false_ (default) if devices should only be reported once. + - _duration_: time in seconds to scan for. (default) Scans forever when not specified. + - _scanMode_: String defines [setScanMode()](https://developer.android.com/reference/kotlin/android/bluetooth/le/ScanSettings.Builder#setscanmode) argument on Android. Default on Android is _lowPower_. When interactive scanning from an app, _lowLatency_ can boost how quickly the device is found, at the expense of using more battery power. Can be one of: _lowPower_ | _balanced_ | _lowLatency_ | _opportunistic_ - _callbackType_: String defines [setCallbackType()](https://developer.android.com/reference/kotlin/android/bluetooth/le/ScanSettings.Builder#setcallbacktype) argument on Android. Can be one of: _all_ | _first_ | _lost_ diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 3bf09e65..c55fd326 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -526,7 +526,8 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback if (reportDelay >= 0L) scanSettings.setReportDelay( reportDelay ); - findLowEnergyDevices(callbackContext, serviceUUIDs, -1, scanSettings.build() ); + int scanDuration = options.optInt("duration", -1); + findLowEnergyDevices(callbackContext, serviceUUIDs, scanDuration, scanSettings.build() ); } } else if (action.equals(BONDED_DEVICES)) { diff --git a/src/ios/BLECentralPlugin.m b/src/ios/BLECentralPlugin.m index a8b1ebc8..db12f111 100644 --- a/src/ios/BLECentralPlugin.m +++ b/src/ios/BLECentralPlugin.m @@ -420,8 +420,17 @@ - (void)startScanWithOptions:(CDVInvokedUrlCommand*)command { [scanOptions setValue:reportDuplicates forKey:CBCentralManagerScanOptionAllowDuplicatesKey]; } + NSNumber *timeoutSeconds = [options valueForKey: @"duration"]; [manager scanForPeripheralsWithServices:serviceUUIDs options:scanOptions]; + + if (timeoutSeconds) { + [NSTimer scheduledTimerWithTimeInterval:[timeoutSeconds floatValue] + target:self + selector:@selector(stopScanTimer:) + userInfo:[command.callbackId copy] + repeats:NO]; + } } - (void)stopScan:(CDVInvokedUrlCommand*)command { diff --git a/types.d.ts b/types.d.ts index 318667db..c3cbfa89 100644 --- a/types.d.ts +++ b/types.d.ts @@ -45,6 +45,8 @@ declare namespace BLECentralPlugin { reportDelay?: number; reportDuplicates?: boolean; + /** Scanning duration in seconds */ + duration?: number; } interface L2CAPOptions { From 6d507fdd1a8f97b5277cc7d180ef5277957be514 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 6 May 2023 10:01:03 +1000 Subject: [PATCH 33/86] Standardise all scans to go via scanWithOptions --- src/android/BLECentralPlugin.java | 22 +--------------- src/browser/BLECentralPlugin.js | 9 +------ src/ios/BLECentralPlugin.h | 2 -- src/ios/BLECentralPlugin.m | 44 ------------------------------- www/ble.js | 12 ++------- 5 files changed, 4 insertions(+), 85 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index c55fd326..516603e8 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -64,8 +64,6 @@ public class BLECentralPlugin extends CordovaPlugin { private static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN" ; // API 31 // actions - private static final String SCAN = "scan"; - private static final String START_SCAN = "startScan"; private static final String STOP_SCAN = "stopScan"; private static final String START_SCAN_WITH_OPTIONS = "startScanWithOptions"; private static final String BONDED_DEVICES = "bondedDevices"; @@ -210,21 +208,7 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback } boolean validAction = true; - - if (action.equals(SCAN)) { - - UUID[] serviceUUIDs = parseServiceUUIDList(args.getJSONArray(0)); - int scanSeconds = args.getInt(1); - resetScanOptions(); - findLowEnergyDevices(callbackContext, serviceUUIDs, scanSeconds); - - } else if (action.equals(START_SCAN)) { - - UUID[] serviceUUIDs = parseServiceUUIDList(args.getJSONArray(0)); - resetScanOptions(); - findLowEnergyDevices(callbackContext, serviceUUIDs, -1); - - } else if (action.equals(STOP_SCAN)) { + if (action.equals(STOP_SCAN)) { stopScan(); callbackContext.success(); @@ -1187,10 +1171,6 @@ public void onScanFailed(int errorCode) { }; - private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] serviceUUIDs, int scanSeconds) { - findLowEnergyDevices( callbackContext, serviceUUIDs, scanSeconds, new ScanSettings.Builder().build() ); - } - private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] serviceUUIDs, int scanSeconds, ScanSettings scanSettings) { if (!locationServicesEnabled() && Build.VERSION.SDK_INT < 31) { diff --git a/src/browser/BLECentralPlugin.js b/src/browser/BLECentralPlugin.js index 76ee6dcf..f2a4c158 100644 --- a/src/browser/BLECentralPlugin.js +++ b/src/browser/BLECentralPlugin.js @@ -16,14 +16,7 @@ function formatUUID(uuid) { module.exports = { deviceInfos: new Map(), - - scan: function(services, seconds, success, failure) { - return this.startScanWithOptions(services, {}, success, failure); - }, - startScan: function(services, success, failure) { - return this.startScanWithOptions(services, {}, success, failure); - }, - startScanWithOptions: function(services, options, success, failure) { + startScanWithOptions: function (services, options, success, failure) { if (!navigator.bluetooth) { failure('Bluetooth is not supported on this browser.'); return; diff --git a/src/ios/BLECentralPlugin.h b/src/ios/BLECentralPlugin.h index e8972846..efa5a41d 100644 --- a/src/ios/BLECentralPlugin.h +++ b/src/ios/BLECentralPlugin.h @@ -43,8 +43,6 @@ @property (strong, nonatomic) NSMutableSet *peripherals; @property (strong, nonatomic) CBCentralManager *manager; -- (void)scan:(CDVInvokedUrlCommand *)command; -- (void)startScan:(CDVInvokedUrlCommand *)command; - (void)startScanWithOptions:(CDVInvokedUrlCommand *)command; - (void)stopScan:(CDVInvokedUrlCommand *)command; - (void)connectedPeripheralsWithServices:(CDVInvokedUrlCommand*)command; diff --git a/src/ios/BLECentralPlugin.m b/src/ios/BLECentralPlugin.m index db12f111..0f41f42c 100644 --- a/src/ios/BLECentralPlugin.m +++ b/src/ios/BLECentralPlugin.m @@ -354,50 +354,6 @@ - (void)isEnabled:(CDVInvokedUrlCommand*)command { [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } -- (void)scan:(CDVInvokedUrlCommand*)command { - NSLog(@"scan"); - if ([manager state] != CBManagerStatePoweredOn) { - NSString *error = @"Bluetooth is disabled"; - NSLog(@"%@", error); - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR - messageAsString:error]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - return; - } - - discoverPeripheralCallbackId = [command.callbackId copy]; - - NSArray *serviceUUIDStrings = [command argumentAtIndex:0]; - NSNumber *timeoutSeconds = [command argumentAtIndex:1]; - NSArray *serviceUUIDs = [self uuidStringsToCBUUIDs:serviceUUIDStrings]; - - [manager scanForPeripheralsWithServices:serviceUUIDs options:nil]; - - [NSTimer scheduledTimerWithTimeInterval:[timeoutSeconds floatValue] - target:self - selector:@selector(stopScanTimer:) - userInfo:[command.callbackId copy] - repeats:NO]; -} - -- (void)startScan:(CDVInvokedUrlCommand*)command { - NSLog(@"startScan"); - if ([manager state] != CBManagerStatePoweredOn) { - NSString *error = @"Bluetooth is disabled"; - NSLog(@"%@", error); - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR - messageAsString:error]; - [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; - return; - } - - discoverPeripheralCallbackId = [command.callbackId copy]; - NSArray *serviceUUIDStrings = [command argumentAtIndex:0]; - NSArray *serviceUUIDs = [self uuidStringsToCBUUIDs:serviceUUIDStrings]; - - [manager scanForPeripheralsWithServices:serviceUUIDs options:nil]; -} - - (void)startScanWithOptions:(CDVInvokedUrlCommand*)command { NSLog(@"startScanWithOptions"); if ([manager state] != CBManagerStatePoweredOn) { diff --git a/www/ble.js b/www/ble.js index 87ba597b..4a0d742a 100644 --- a/www/ble.js +++ b/www/ble.js @@ -51,19 +51,11 @@ var autoconnected = {}; module.exports = { scan: function (services, seconds, success, failure) { - var successWrapper = function (peripheral) { - convertToNativeJS(peripheral); - success(peripheral); - }; - cordova.exec(successWrapper, failure, 'BLE', 'scan', [services, seconds]); + module.exports.startScanWithOptions(services, { duration: seconds }, success, failure); }, startScan: function (services, success, failure) { - var successWrapper = function (peripheral) { - convertToNativeJS(peripheral); - success(peripheral); - }; - cordova.exec(successWrapper, failure, 'BLE', 'startScan', [services]); + module.exports.startScanWithOptions(services, undefined, success, failure); }, stopScan: function (success, failure) { From 47e8f09cf4f91e920c3d045dacc607119b2119be Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 6 May 2023 16:58:31 +1000 Subject: [PATCH 34/86] Create GitHub Capacitor Actions (#969) --- .github/workflows/capacitor.yaml | 83 ++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 .github/workflows/capacitor.yaml diff --git a/.github/workflows/capacitor.yaml b/.github/workflows/capacitor.yaml new file mode 100644 index 00000000..2045893f --- /dev/null +++ b/.github/workflows/capacitor.yaml @@ -0,0 +1,83 @@ +name: Capacitor + +on: + workflow_dispatch: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + capacitor-android: + name: android@${{ matrix.capacitor }} + + runs-on: ubuntu-latest + + strategy: + matrix: + include: + - jdk: 17 + capacitor: latest + node: 18.x + - jdk: 11 + capacitor: 4 + node: 14.x + + steps: + - uses: actions/checkout@v3 + - name: set up JDK ${{ matrix.jdk }} + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.jdk }} + distribution: 'temurin' + - name: Use Node.js ${{ matrix.node }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + - name: Build test app + run: | + mkdir temp + cd temp + mkdir www + touch www/index.html + npm init -y + npm install .. + npm install @capacitor/cli@${{ matrix.capacitor }} @capacitor/core@${{ matrix.capacitor }} @capacitor/android@${{ matrix.capacitor }} + npx cap init test io.test.app + npx cap add android + npx cap sync android + cd android + chmod +x gradlew + ./gradlew build + + + capacitor-ios: + name: ios@${{ matrix.capacitor }} + runs-on: macos-latest + + strategy: + matrix: + include: + - capacitor: latest + node: 18.x + + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + - name: Build test app + run: | + mkdir temp + cd temp + mkdir www + touch www/index.html + npm init -y + npm install .. + npm install @capacitor/cli@${{ matrix.capacitor }} @capacitor/core@${{ matrix.capacitor }} @capacitor/ios@${{ matrix.capacitor }} + npx cap init test io.test.app + npx cap add ios + npx cap sync ios + cd ios + xcodebuild -workspace App/App.xcworkspace -scheme CapacitorCordova -configuration Debug -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 14 Pro Max" From 2349ef07b54afe2d4d30dadfbfd43e307a147490 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 6 May 2023 17:04:40 +1000 Subject: [PATCH 35/86] Update cordova.yml (#970) --- .github/workflows/cordova.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cordova.yml b/.github/workflows/cordova.yml index edc2b09c..cbb30f00 100644 --- a/.github/workflows/cordova.yml +++ b/.github/workflows/cordova.yml @@ -21,12 +21,12 @@ jobs: platform: latest node: 18.x - jdk: 11 - cordova: 11.1.0 - platform: 10.1.2 + cordova: 11 + platform: 10 node: 14.x - jdk: 8 - cordova: 10.0.0 - platform: 10.1.2 + cordova: 10 + platform: 10 node: 14.x steps: From 2d933e5ce277844dc5f739314ed10fbd592aeb85 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Fri, 19 May 2023 18:11:07 +1000 Subject: [PATCH 36/86] Android: Ensure scan timeout is cleared and reset when new scans are started The this::stopScan syntax seems to lose the exact reference for the runnable, preventing the remove callback from properly clearing it. --- src/android/BLECentralPlugin.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 516603e8..b1662a46 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -144,6 +144,7 @@ public class BLECentralPlugin extends CordovaPlugin { private int scanSeconds; private ScanSettings scanSettings; private final Handler stopScanHandler = new Handler(Looper.getMainLooper()); + private final Runnable stopScanRunnable = this::stopScan; // Bluetooth state notification CallbackContext stateCallback; @@ -1254,11 +1255,11 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic filters.add(filter); } } - stopScanHandler.removeCallbacks(this::stopScan); + stopScanHandler.removeCallbacks(stopScanRunnable); bluetoothLeScanner.startScan(filters, scanSettings, leScanCallback); if (scanSeconds > 0) { - stopScanHandler.postDelayed(this::stopScan, scanSeconds * 1000); + stopScanHandler.postDelayed(stopScanRunnable, scanSeconds * 1000); } PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT); @@ -1267,7 +1268,7 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic } private void stopScan() { - stopScanHandler.removeCallbacks(this::stopScan); + stopScanHandler.removeCallbacks(stopScanRunnable); if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { LOG.d(TAG, "Stopping Scan"); try { From b728ffcb39b6b40aa4e89c67d71f05801456a487 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 20 May 2023 00:16:08 +1000 Subject: [PATCH 37/86] iOS: Ensure scan timeout is cleared and reset when new scans are started --- src/ios/BLECentralPlugin.h | 1 + src/ios/BLECentralPlugin.m | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/ios/BLECentralPlugin.h b/src/ios/BLECentralPlugin.h index efa5a41d..e1223dbf 100644 --- a/src/ios/BLECentralPlugin.h +++ b/src/ios/BLECentralPlugin.h @@ -38,6 +38,7 @@ NSMutableDictionary *readRSSICallbacks; NSDictionary *restoredState; NSMutableDictionary *l2CapContexts; + NSTimer *scanTimer; } @property (strong, nonatomic) NSMutableSet *peripherals; diff --git a/src/ios/BLECentralPlugin.m b/src/ios/BLECentralPlugin.m index 0f41f42c..4ca18cec 100644 --- a/src/ios/BLECentralPlugin.m +++ b/src/ios/BLECentralPlugin.m @@ -380,8 +380,10 @@ - (void)startScanWithOptions:(CDVInvokedUrlCommand*)command { [manager scanForPeripheralsWithServices:serviceUUIDs options:scanOptions]; + [scanTimer invalidate]; + scanTimer = nil; if (timeoutSeconds) { - [NSTimer scheduledTimerWithTimeInterval:[timeoutSeconds floatValue] + scanTimer = [NSTimer scheduledTimerWithTimeInterval:[timeoutSeconds floatValue] target:self selector:@selector(stopScanTimer:) userInfo:[command.callbackId copy] @@ -391,7 +393,9 @@ - (void)startScanWithOptions:(CDVInvokedUrlCommand*)command { - (void)stopScan:(CDVInvokedUrlCommand*)command { NSLog(@"stopScan"); - + [scanTimer invalidate]; + scanTimer = nil; + if ([manager state] == CBManagerStatePoweredOn) { [manager stopScan]; } @@ -604,7 +608,8 @@ - (void)writeL2Cap:(CDVInvokedUrlCommand *)command { -(void)stopScanTimer:(NSTimer *)timer { NSLog(@"stopScanTimer"); - + [scanTimer invalidate]; + scanTimer = nil; [manager stopScan]; if (discoverPeripheralCallbackId) { From 3946f97c66bcc70b2771dc6093343861bb7b5b63 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 20 May 2023 10:58:37 +1000 Subject: [PATCH 38/86] Prevent API warnings on iOS if Bluetooth is disabled during timed scan --- src/ios/BLECentralPlugin.m | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/ios/BLECentralPlugin.m b/src/ios/BLECentralPlugin.m index 4ca18cec..f8b12148 100644 --- a/src/ios/BLECentralPlugin.m +++ b/src/ios/BLECentralPlugin.m @@ -393,12 +393,7 @@ - (void)startScanWithOptions:(CDVInvokedUrlCommand*)command { - (void)stopScan:(CDVInvokedUrlCommand*)command { NSLog(@"stopScan"); - [scanTimer invalidate]; - scanTimer = nil; - - if ([manager state] == CBManagerStatePoweredOn) { - [manager stopScan]; - } + [self internalStopScan]; if (discoverPeripheralCallbackId) { discoverPeripheralCallbackId = nil; @@ -608,13 +603,7 @@ - (void)writeL2Cap:(CDVInvokedUrlCommand *)command { -(void)stopScanTimer:(NSTimer *)timer { NSLog(@"stopScanTimer"); - [scanTimer invalidate]; - scanTimer = nil; - [manager stopScan]; - - if (discoverPeripheralCallbackId) { - discoverPeripheralCallbackId = nil; - } + [self internalStopScan]; } #pragma mark - CBCentralManagerDelegate @@ -1221,4 +1210,17 @@ - (id) tryDecodeBinaryData:(id)value { return value; } +- (void) internalStopScan { + [scanTimer invalidate]; + scanTimer = nil; + + if ([manager state] == CBManagerStatePoweredOn) { + [manager stopScan]; + } + + if (discoverPeripheralCallbackId) { + discoverPeripheralCallbackId = nil; + } +} + @end From f4039b05b5b1767bb98f2f99ca6e26324194d4f6 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 20 May 2023 15:43:36 +1000 Subject: [PATCH 39/86] Reformat plugin.xml using @xmldom/xmldom (no content changes) --- plugin.xml | 71 +++++++++++++++++++++++------------------------------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/plugin.xml b/plugin.xml index 6b5e6747..6660af7f 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,9 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin @@ -14,36 +10,36 @@ https://github.com/don/cordova-plugin-ble-central/issues - + - - + + - - + + - - + + - - + + - - + + - + - + $BLUETOOTH_USAGE_DESCRIPTION @@ -57,7 +53,7 @@ - + @@ -66,12 +62,12 @@ - - - - - - + + + + + + @@ -80,25 +76,18 @@ - - - - - - - + + + + + + + - + - + \ No newline at end of file From 501f54b76ac8f186f217c6d4f5d38a4aed0afb10 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 20 May 2023 15:53:56 +1000 Subject: [PATCH 40/86] Clean up npm package to exclude unnecessary bits --- .npmignore | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..dd69d6fa --- /dev/null +++ b/.npmignore @@ -0,0 +1,11 @@ +.idea/ +*.DS_Store +node_modules +package-lock.json +.github +.vscode +examples +tests +tools +.prettierrc +cordova-plugin-ble-central.iml \ No newline at end of file From e721cf6d4de0bbb6f7423d3d4e147f99091c3da2 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 20 May 2023 15:20:30 +1000 Subject: [PATCH 41/86] Automatically align versions and add tooling for making slim variant --- .gitignore | 2 ++ package.json | 9 ++++++++- tools/make-slim-variant.mjs | 30 ++++++++++++++++++++++++++++++ tools/set-plugin-version.mjs | 11 +++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 tools/make-slim-variant.mjs create mode 100644 tools/set-plugin-version.mjs diff --git a/.gitignore b/.gitignore index ee496a4b..8b1af2bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .idea/ *.DS_Store +node_modules +package-lock.json \ No newline at end of file diff --git a/package.json b/package.json index a326aaa9..05b9d78b 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,10 @@ "name": "cordova-plugin-ble-central", "version": "1.6.3", "description": "Bluetooth Low Energy (BLE) Central Plugin", + "scripts": { + "slimify": "node tools/make-slim-variant.mjs", + "version": "node tools/set-plugin-version.mjs && git add plugin.xml" + }, "cordova": { "id": "cordova-plugin-ble-central", "platforms": [ @@ -36,5 +40,8 @@ "url": "https://github.com/don/cordova-plugin-ble-central/issues" }, "types": "./types.d.ts", - "homepage": "https://github.com/don/cordova-plugin-ble-central#readme" + "homepage": "https://github.com/don/cordova-plugin-ble-central#readme", + "devDependencies": { + "@xmldom/xmldom": "^0.8.7" + } } diff --git a/tools/make-slim-variant.mjs b/tools/make-slim-variant.mjs new file mode 100644 index 00000000..6858c781 --- /dev/null +++ b/tools/make-slim-variant.mjs @@ -0,0 +1,30 @@ +import { readFileSync, writeFileSync, rmSync } from 'fs'; +import { DOMParser, XMLSerializer } from '@xmldom/xmldom'; + +const { version } = JSON.parse(readFileSync('package.json', 'utf8')); + +const doc = new DOMParser().parseFromString(readFileSync('plugin.xml', 'utf-8'), 'text/xml'); +const pluginXml = doc.documentElement; +pluginXml.setAttribute('version', version); +console.log('Changed plugin.xml version to ', pluginXml.getAttribute('version')); + +const configFileEls = pluginXml.getElementsByTagName('config-file'); +for (const configFileEl of Array.from(configFileEls)) { + if (configFileEl.getAttribute('target') == 'AndroidManifest.xml') { + configFileEl.parentNode.removeChild(configFileEl); + break; + } +} +console.log('Removed plugin.xml config-file[target=AndroidManifest] node'); + +for (const hookEl of Array.from(pluginXml.getElementsByTagName('hook'))) { + hookEl.parentNode.removeChild(hookEl); +} +console.log('Removed plugin.xml hook'); +writeFileSync('plugin.xml', new XMLSerializer().serializeToString(doc)); + +rmSync('hooks', { recursive: true }); +console.log('Removed hooks folder'); + +rmSync('stripDuplicatePermissions.js'); +console.log('Removed stripDuplicatePermissions hook'); diff --git a/tools/set-plugin-version.mjs b/tools/set-plugin-version.mjs new file mode 100644 index 00000000..dbe8fc77 --- /dev/null +++ b/tools/set-plugin-version.mjs @@ -0,0 +1,11 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { DOMParser, XMLSerializer } from '@xmldom/xmldom'; + +const { version } = JSON.parse(readFileSync('package.json', 'utf8')); + +const doc = new DOMParser().parseFromString(readFileSync('plugin.xml', 'utf-8'), 'text/xml'); +const pluginXml = doc.documentElement; +pluginXml.setAttribute('version', version); +writeFileSync('plugin.xml', new XMLSerializer().serializeToString(doc)); + +console.log('Changed plugin.xml version to ', pluginXml.getAttribute('version')); From 924f5c6dce0ea875c0e4492e0d35a047ab5143bd Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 20 May 2023 17:24:19 +1000 Subject: [PATCH 42/86] Update commit instructions --- CHANGES.txt | 10 ++++++++++ README.md | 10 +++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index f3662e25..5e4f1a53 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,13 @@ += 1.7.0 = +iOS: Prevent API warnings on iOS if Bluetooth is disabled during timed scan +iOS: Ensure scan timeout is cleared and reset when new scans are started +Android: Ensure scan timeout is cleared and reset when new scans are started +Standardise all scans to go via scanWithOptions +Add duration flag (in seconds) to scan with options +CI: Create GitHub Cordova & Capacitor Actions (#969) +Documentation: update readme with info for advertisement parsing module +Documentation: Detail capacitor installation instructions + = 1.6.3 = Android: Implement manual bond control on #605 #843 diff --git a/README.md b/README.md index c756387c..de323646 100644 --- a/README.md +++ b/README.md @@ -1443,20 +1443,20 @@ Run the app on your phone 1. `npm version prepatch --preid=alpha` 2. Align `plugin.xml` version with npm version -3. `npm publish --tag alpha --registry=https://registry.npmjs.org` +3. `npm publish --tag alpha` ## Release 1. `npm version patch` 2. Align `plugin.xml` version with npm version -3. Update release notes -4. `npm publish --registry=https://registry.npmjs.org` +3. Update release notes based on `git log --oneline --no-merges ...master` +4. `npm publish` -## Release (lean) +## Release (slim) 1. `git merge master` 2. Align `package.json` and `plugin.xml` versions -3. `npm publish --tag lean --registry=https://registry.npmjs.org` +3. `npm publish --tag slim` # Nordic DFU From 7cabaab55ec920274846121651770410bb011809 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 20 May 2023 17:24:42 +1000 Subject: [PATCH 43/86] 1.7.0 --- package.json | 2 +- plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 05b9d78b..48003fe0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.6.3", + "version": "1.7.0", "description": "Bluetooth Low Energy (BLE) Central Plugin", "scripts": { "slimify": "node tools/make-slim-variant.mjs", diff --git a/plugin.xml b/plugin.xml index 6660af7f..46ba0669 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin From 086e75f7115cb32193c6548e632edb5fbd1c4f03 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 20 May 2023 17:38:21 +1000 Subject: [PATCH 44/86] Tweaks to make slim variant producer simpler to use --- tools/make-slim-variant.mjs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/make-slim-variant.mjs b/tools/make-slim-variant.mjs index 6858c781..d3257289 100644 --- a/tools/make-slim-variant.mjs +++ b/tools/make-slim-variant.mjs @@ -1,7 +1,10 @@ import { readFileSync, writeFileSync, rmSync } from 'fs'; import { DOMParser, XMLSerializer } from '@xmldom/xmldom'; -const { version } = JSON.parse(readFileSync('package.json', 'utf8')); +const packageJson = JSON.parse(readFileSync('package.json', 'utf8')); +packageJson.version += '-slim'; +writeFileSync('package.json', JSON.stringify(packageJson, undefined, ' ') + '\n'); +const version = packageJson.version; const doc = new DOMParser().parseFromString(readFileSync('plugin.xml', 'utf-8'), 'text/xml'); const pluginXml = doc.documentElement; @@ -23,8 +26,8 @@ for (const hookEl of Array.from(pluginXml.getElementsByTagName('hook'))) { console.log('Removed plugin.xml hook'); writeFileSync('plugin.xml', new XMLSerializer().serializeToString(doc)); -rmSync('hooks', { recursive: true }); +rmSync('hooks', { recursive: true, force: true }); console.log('Removed hooks folder'); -rmSync('stripDuplicatePermissions.js'); +rmSync('stripDuplicatePermissions.js', { force: true }); console.log('Removed stripDuplicatePermissions hook'); From 633d8899dc7da91c130c626f78a9fd33a416f601 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 7 Jun 2023 10:34:38 +1000 Subject: [PATCH 45/86] Re-indent yaml file --- .github/workflows/capacitor.yaml | 99 ++++++++++++++++---------------- .prettierrc | 6 ++ 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/.github/workflows/capacitor.yaml b/.github/workflows/capacitor.yaml index 2045893f..7a9b0a09 100644 --- a/.github/workflows/capacitor.yaml +++ b/.github/workflows/capacitor.yaml @@ -3,16 +3,16 @@ name: Capacitor on: workflow_dispatch: push: - branches: [ "master" ] + branches: ['master'] pull_request: - branches: [ "master" ] + branches: ['master'] jobs: capacitor-android: name: android@${{ matrix.capacitor }} - + runs-on: ubuntu-latest - + strategy: matrix: include: @@ -24,37 +24,36 @@ jobs: node: 14.x steps: - - uses: actions/checkout@v3 - - name: set up JDK ${{ matrix.jdk }} - uses: actions/setup-java@v3 - with: - java-version: ${{ matrix.jdk }} - distribution: 'temurin' - - name: Use Node.js ${{ matrix.node }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node }} - - name: Build test app - run: | - mkdir temp - cd temp - mkdir www - touch www/index.html - npm init -y - npm install .. - npm install @capacitor/cli@${{ matrix.capacitor }} @capacitor/core@${{ matrix.capacitor }} @capacitor/android@${{ matrix.capacitor }} - npx cap init test io.test.app - npx cap add android - npx cap sync android - cd android - chmod +x gradlew - ./gradlew build - + - uses: actions/checkout@v3 + - name: set up JDK ${{ matrix.jdk }} + uses: actions/setup-java@v3 + with: + java-version: ${{ matrix.jdk }} + distribution: 'temurin' + - name: Use Node.js ${{ matrix.node }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + - name: Build test app + run: | + mkdir temp + cd temp + mkdir www + touch www/index.html + npm init -y + npm install .. + npm install @capacitor/cli@${{ matrix.capacitor }} @capacitor/core@${{ matrix.capacitor }} @capacitor/android@${{ matrix.capacitor }} + npx cap init test io.test.app + npx cap add android + npx cap sync android + cd android + chmod +x gradlew + ./gradlew build capacitor-ios: name: ios@${{ matrix.capacitor }} runs-on: macos-latest - + strategy: matrix: include: @@ -62,22 +61,22 @@ jobs: node: 18.x steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node }} - - name: Build test app - run: | - mkdir temp - cd temp - mkdir www - touch www/index.html - npm init -y - npm install .. - npm install @capacitor/cli@${{ matrix.capacitor }} @capacitor/core@${{ matrix.capacitor }} @capacitor/ios@${{ matrix.capacitor }} - npx cap init test io.test.app - npx cap add ios - npx cap sync ios - cd ios - xcodebuild -workspace App/App.xcworkspace -scheme CapacitorCordova -configuration Debug -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 14 Pro Max" + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node }} + - name: Build test app + run: | + mkdir temp + cd temp + mkdir www + touch www/index.html + npm init -y + npm install .. + npm install @capacitor/cli@${{ matrix.capacitor }} @capacitor/core@${{ matrix.capacitor }} @capacitor/ios@${{ matrix.capacitor }} + npx cap init test io.test.app + npx cap add ios + npx cap sync ios + cd ios + xcodebuild -workspace App/App.xcworkspace -scheme CapacitorCordova -configuration Debug -sdk iphonesimulator -destination "platform=iOS Simulator,name=iPhone 14 Pro Max" diff --git a/.prettierrc b/.prettierrc index 050a0b8e..9f257a4b 100644 --- a/.prettierrc +++ b/.prettierrc @@ -15,6 +15,12 @@ "options": { "tabWidth": 2 } + }, + { + "files": "*.yaml", + "options": { + "tabWidth": 2 + } } ] } From 73aa72ee32c560e6e301af8000c592306eecf380 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 7 Jun 2023 10:35:18 +1000 Subject: [PATCH 46/86] Android: Add androidx.annotation reference for RequiresPermission attribute (#977) --- .github/workflows/capacitor.yaml | 3 +++ plugin.xml | 1 + 2 files changed, 4 insertions(+) diff --git a/.github/workflows/capacitor.yaml b/.github/workflows/capacitor.yaml index 7a9b0a09..622a3221 100644 --- a/.github/workflows/capacitor.yaml +++ b/.github/workflows/capacitor.yaml @@ -22,6 +22,9 @@ jobs: - jdk: 11 capacitor: 4 node: 14.x + - jdk: 8 + capacitor: 3 + node: 12.x steps: - uses: actions/checkout@v3 diff --git a/plugin.xml b/plugin.xml index 46ba0669..3e12b634 100644 --- a/plugin.xml +++ b/plugin.xml @@ -83,6 +83,7 @@ + From 3df3c4edce16c05c534c2c31c9baf3c825816952 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 7 Jun 2023 10:51:26 +1000 Subject: [PATCH 47/86] Bump capacitor3 builds to node 14 Avoids a build issue: node_modules\@ionic\utils-subprocess\dist\index.js:72 promise.p.stdout?.pipe(stdoutBuf); ^ SyntaxError: Unexpected token '.' at wrapSafe (internal/modules/cjs/loader.js:915:16) at Module._compile (internal/modules/cjs/loader.js:963:27) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10) at Module.load (internal/modules/cjs/loader.js:863:32) at Function.Module._load (internal/modules/cjs/loader.js:708:14) at Module.require (internal/modules/cjs/loader.js:887:19) at require (internal/modules/cjs/helpers.js:74:18) at Object. (J:\temp\temp\node_modules\@capacitor\cli\dist\ipc.js:6:28) at Module._compile (internal/modules/cjs/loader.js:999:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10) --- .github/workflows/capacitor.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/capacitor.yaml b/.github/workflows/capacitor.yaml index 622a3221..5f9bfc00 100644 --- a/.github/workflows/capacitor.yaml +++ b/.github/workflows/capacitor.yaml @@ -24,7 +24,7 @@ jobs: node: 14.x - jdk: 8 capacitor: 3 - node: 12.x + node: 14.x steps: - uses: actions/checkout@v3 From b4c7e7af789ef0633a9d7a173b8ed3dd01b2ba34 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 7 Jun 2023 11:15:19 +1000 Subject: [PATCH 48/86] Disable capacitor3 builds again Once using the right node version still results in a permissions challenge: ERROR:/home/runner/work/cordova-plugin-ble-central/cordova-plugin-ble-central/temp/android/app/build/intermediates/packaged_manifests/debug/AndroidManifest.xml:26: AAPT: error: attribute android:usesPermissionFlags not found. --- .github/workflows/capacitor.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/capacitor.yaml b/.github/workflows/capacitor.yaml index 5f9bfc00..7a9b0a09 100644 --- a/.github/workflows/capacitor.yaml +++ b/.github/workflows/capacitor.yaml @@ -22,9 +22,6 @@ jobs: - jdk: 11 capacitor: 4 node: 14.x - - jdk: 8 - capacitor: 3 - node: 14.x steps: - uses: actions/checkout@v3 From b85026993103810feb8091212a0914e40fc2e774 Mon Sep 17 00:00:00 2001 From: younesspotmaster <142569654+younesspotmaster@users.noreply.github.com> Date: Thu, 21 Sep 2023 10:45:13 +0200 Subject: [PATCH 49/86] Implement forceScanFilter for Android (#989) * Implement forceScanFilter for Android * Only forceScanFilter when no uuids are provided --------- Co-authored-by: Younes --- README.md | 3 ++- src/android/BLECentralPlugin.java | 7 +++++++ types.d.ts | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index de323646..ac9dcb37 100644 --- a/README.md +++ b/README.md @@ -255,9 +255,10 @@ See the [location permission notes](#location-permission-notes) above for inform Can be one of: _aggressive_ | _sticky_ - _numOfMatches_: String defines [setNumOfMatches()](https://developer.android.com/reference/kotlin/android/bluetooth/le/ScanSettings.Builder#setnumofmatches) argument on Android. Can be one of: _one_ | _few_ | _max_ - - _phy_: String for [setPhy()](https://developer.android.com/reference/kotlin/android/bluetooth/le/ScanSettings.Builder#setphy) on Android. + - _phy_: String for [setPhy()](https://developer.android.com/reference/kotlin/android/bluetooth/le/ScanSettings.Builder#setphy) on Android. Can be one of: _1m_ | _coded_ | _all_ - _legacy_: _true_ or _false_ to [control filtering](https://developer.android.com/reference/kotlin/android/bluetooth/le/ScanSettings.Builder#setlegacy) bluetooth spec.pre-4.2 advertisements on Android. + - _forceScanFilter_: _true_ or _false_ to [to force an empty scan filter](https://github.com/don/cordova-plugin-ble-central/issues/987) if no other filters are provided on Android. - _reportDelay_: Milliseconds for [setReportDelay()](https://developer.android.com/reference/kotlin/android/bluetooth/le/ScanSettings.Builder#setreportdelay) on Android. _0_ to be notified of results immediately. Values > _0_ causes the scan results to be queued up and delivered after the requested delay or when the internal buffers fill up. - **success**: Success callback function that is invoked which each discovered device. - **failure**: Error callback function, invoked when error occurs. [optional] diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index b1662a46..0d820c64 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -127,6 +127,7 @@ public class BLECentralPlugin extends CordovaPlugin { // scan options boolean reportDuplicates = false; + boolean forceScanFilter = false; private static final int REQUEST_BLUETOOTH_SCAN = 2; private static final int REQUEST_BLUETOOTH_CONNECT = 3; @@ -410,6 +411,7 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback resetScanOptions(); this.reportDuplicates = options.optBoolean("reportDuplicates", false); + this.forceScanFilter = options.optBoolean("forceScanFilter", false); ScanSettings.Builder scanSettings = new ScanSettings.Builder(); switch (options.optString("scanMode", "")) { @@ -1254,7 +1256,11 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic new ParcelUuid(uuid)).build(); filters.add(filter); } + } else if (this.forceScanFilter) { + ScanFilter filter = new ScanFilter.Builder().build(); + filters.add(filter); } + stopScanHandler.removeCallbacks(stopScanRunnable); bluetoothLeScanner.startScan(filters, scanSettings, leScanCallback); @@ -1450,6 +1456,7 @@ private UUID uuidFromString(String uuid) { */ private void resetScanOptions() { this.reportDuplicates = false; + this.forceScanFilter = false; } private void addBondStateListener() { diff --git a/types.d.ts b/types.d.ts index c3cbfa89..18a33985 100644 --- a/types.d.ts +++ b/types.d.ts @@ -43,6 +43,8 @@ declare namespace BLECentralPlugin { legacy?: boolean; /* Android only */ reportDelay?: number; + /* Android only */ + forceScanFilter?: number; reportDuplicates?: boolean; /** Scanning duration in seconds */ From 12e4f641031445660716632c0be2422276bbd3f1 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Thu, 21 Sep 2023 18:47:27 +1000 Subject: [PATCH 50/86] 1.7.1 --- CHANGES.txt | 3 +++ package.json | 2 +- plugin.xml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 5e4f1a53..88be936d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ += 1.7.1 = +Android: Add forceScanFilter option for Android (#989, #987) thanks younesspotmaster + = 1.7.0 = iOS: Prevent API warnings on iOS if Bluetooth is disabled during timed scan iOS: Ensure scan timeout is cleared and reset when new scans are started diff --git a/package.json b/package.json index 48003fe0..110c614f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.7.0", + "version": "1.7.1", "description": "Bluetooth Low Energy (BLE) Central Plugin", "scripts": { "slimify": "node tools/make-slim-variant.mjs", diff --git a/plugin.xml b/plugin.xml index 3e12b634..cfc3700e 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin From 8b826e146dd905e32d842ea1a6382a50fba31d3c Mon Sep 17 00:00:00 2001 From: Tobias Neumann Date: Fri, 3 Nov 2023 23:05:52 +0100 Subject: [PATCH 51/86] Feature Request: isConnectable Property for Android #823 (#993) --- README.md | 1 + src/android/BLECentralPlugin.java | 23 ++++++++++++++++------- src/android/Peripheral.java | 22 +++++++++++++++++++++- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ac9dcb37..37cd9fe7 100644 --- a/README.md +++ b/README.md @@ -1308,6 +1308,7 @@ the [ble-central-advertisements](https://github.com/jospete/ble-central-advertis "id": "00:1A:7D:DA:71:13", "advertising": ArrayBuffer, "rssi": -37 + "connectable":"true" /*Only on Android >= API Level 26*/ } Convert the advertising info to a Uint8Array for processing. `var adData = new Uint8Array(peripheral.advertising)`. You application is responsible for parsing all the information out of the advertising ArrayBuffer using the [GAP type constants](https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile). For example to get the service data from the advertising info, I [parse the advertising info into a map](https://github.com/don/ITP-BluetoothLE/blob/887511c375b1ab2fbef3afe210d6a6b7db44cee9/phonegap/thermometer_v2/www/js/index.js#L18-L39) and then get the service data to retrieve a [characteristic value that is being broadcast](https://github.com/don/ITP-BluetoothLE/blob/887511c375b1ab2fbef3afe210d6a6b7db44cee9/phonegap/thermometer_v2/www/js/index.js#L93-L103). diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 0d820c64..08953858 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -617,13 +617,13 @@ private void onBluetoothStateChange(Intent intent) { final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); sendBluetoothStateChange(state); if (state == BluetoothAdapter.STATE_OFF) { - // #894 When Bluetooth is physically turned off the whole process might die, so the normal + // #894 When Bluetooth is physically turned off the whole process might die, so the normal // onConnectionStateChange callbacks won't be invoked - + BluetoothManager bluetoothManager = (BluetoothManager) cordova.getActivity().getSystemService(Context.BLUETOOTH_SERVICE); for(Peripheral peripheral : peripherals.values()) { if (!peripheral.isConnected()) continue; - + int connectedState = bluetoothManager.getConnectionState(peripheral.getDevice(), BluetoothProfile.GATT); if (connectedState == BluetoothProfile.STATE_DISCONNECTED) { peripheral.peripheralDisconnected("Bluetooth Disabled"); @@ -1140,7 +1140,12 @@ public void onScanResult(int callbackType, ScanResult result) { if (!alreadyReported) { - Peripheral peripheral = new Peripheral(device, result.getRssi(), result.getScanRecord().getBytes()); + Peripheral peripheral = null; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + peripheral = new Peripheral(device, result.getRssi(), result.getScanRecord().getBytes(),result.isConnectable()); + }else{ + peripheral = new Peripheral(device, result.getRssi(), result.getScanRecord().getBytes()); + } peripherals.put(device.getAddress(), peripheral); if (discoverCallback != null) { @@ -1152,7 +1157,11 @@ public void onScanResult(int callbackType, ScanResult result) { } else { Peripheral peripheral = peripherals.get(address); if (peripheral != null) { - peripheral.update(result.getRssi(), result.getScanRecord().getBytes()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + peripheral.update(result.getRssi(), result.getScanRecord().getBytes(),result.isConnectable()); + }else{ + peripheral.update(result.getRssi(), result.getScanRecord().getBytes()); + } if (reportDuplicates && discoverCallback != null) { PluginResult pluginResult = new PluginResult(PluginResult.Status.OK, peripheral.asJSONObject()); pluginResult.setKeepCallback(true); @@ -1279,7 +1288,7 @@ private void stopScan() { LOG.d(TAG, "Stopping Scan"); try { final BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); - if (bluetoothLeScanner != null) + if (bluetoothLeScanner != null) bluetoothLeScanner.stopScan(leScanCallback); } catch (Exception e) { LOG.e(TAG, "Exception stopping scan", e); @@ -1468,7 +1477,7 @@ public void onReceive(Context context, Intent intent) { if (ACTION_BOND_STATE_CHANGED.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); Peripheral peripheral = peripherals.get(device.getAddress()); - + if (peripheral != null) { int bondState = intent.getIntExtra(EXTRA_BOND_STATE, BluetoothDevice.ERROR); int previousBondState = intent.getIntExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, -1); diff --git a/src/android/Peripheral.java b/src/android/Peripheral.java index 9b7b15a9..46acf821 100644 --- a/src/android/Peripheral.java +++ b/src/android/Peripheral.java @@ -53,6 +53,7 @@ public class Peripheral extends BluetoothGattCallback { private BluetoothDevice device; private byte[] advertisingData; + private boolean isConnectable = true; private int advertisingRSSI; private boolean autoconnect = false; private boolean connected = false; @@ -83,6 +84,13 @@ public Peripheral(BluetoothDevice device) { } + public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord, boolean isConnectable) { + this.device = device; + this.advertisingRSSI = advertisingRSSI; + this.advertisingData = scanRecord; + this.isConnectable = isConnectable; + } + public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord) { this.device = device; @@ -280,6 +288,10 @@ public JSONObject asJSONObject() { if (advertisingRSSI != FAKE_PERIPHERAL_RSSI) { json.put("rssi", advertisingRSSI); } + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { + json.put("connectable", this.isConnectable); + } } catch (JSONException e) { // this shouldn't happen e.printStackTrace(); } @@ -515,11 +527,19 @@ public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { } // Update rssi and scanRecord. + public void update(int rssi, byte[] scanRecord, boolean isConnectable) { + this.advertisingRSSI = rssi; + this.advertisingData = scanRecord; + this.isConnectable = isConnectable; + } + + public void update(int rssi, byte[] scanRecord) { this.advertisingRSSI = rssi; this.advertisingData = scanRecord; } + public void updateRssi(int rssi) { advertisingRSSI = rssi; } @@ -1024,7 +1044,7 @@ public void bond(CallbackContext callbackContext, BluetoothAdapter bluetoothAdap } } } - + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public void unbond(CallbackContext callbackContext) { final int bondState = device.getBondState(); From 912ce83d82de55eae21ffbe40529cf2b67abc668 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 4 Nov 2023 09:15:11 +1100 Subject: [PATCH 52/86] Add connectable flag to type definitions * Reduce version branching slightly by using nullable --- CHANGES.txt | 3 +++ src/android/BLECentralPlugin.java | 9 ++++----- src/android/Peripheral.java | 16 ++++------------ types.d.ts | 2 ++ 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 88be936d..f7f1492d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ += 1.7.2 = +Android: add isConnectable Property for API26+ (O) #823 (#993) thanks @Gargamil + = 1.7.1 = Android: Add forceScanFilter option for Android (#989, #987) thanks younesspotmaster diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 08953858..ec755343 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -1139,13 +1139,12 @@ public void onScanResult(int callbackType, ScanResult result) { boolean alreadyReported = peripherals.containsKey(address) && !peripherals.get(address).isUnscanned(); if (!alreadyReported) { - - Peripheral peripheral = null; + Boolean isConnectable = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - peripheral = new Peripheral(device, result.getRssi(), result.getScanRecord().getBytes(),result.isConnectable()); - }else{ - peripheral = new Peripheral(device, result.getRssi(), result.getScanRecord().getBytes()); + isConnectable = result.isConnectable(); } + + Peripheral peripheral = new Peripheral(device, result.getRssi(), result.getScanRecord().getBytes(), isConnectable); peripherals.put(device.getAddress(), peripheral); if (discoverCallback != null) { diff --git a/src/android/Peripheral.java b/src/android/Peripheral.java index 46acf821..43cddb5b 100644 --- a/src/android/Peripheral.java +++ b/src/android/Peripheral.java @@ -53,7 +53,7 @@ public class Peripheral extends BluetoothGattCallback { private BluetoothDevice device; private byte[] advertisingData; - private boolean isConnectable = true; + private Boolean isConnectable = null; private int advertisingRSSI; private boolean autoconnect = false; private boolean connected = false; @@ -84,21 +84,13 @@ public Peripheral(BluetoothDevice device) { } - public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord, boolean isConnectable) { + public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord, Boolean isConnectable) { this.device = device; this.advertisingRSSI = advertisingRSSI; this.advertisingData = scanRecord; this.isConnectable = isConnectable; } - public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord) { - - this.device = device; - this.advertisingRSSI = advertisingRSSI; - this.advertisingData = scanRecord; - - } - private void gattConnect() { closeGatt(); @@ -289,8 +281,8 @@ public JSONObject asJSONObject() { json.put("rssi", advertisingRSSI); } - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - json.put("connectable", this.isConnectable); + if (this.isConnectable != null) { + json.put("connectable", this.isConnectable.booleanValue()); } } catch (JSONException e) { // this shouldn't happen e.printStackTrace(); diff --git a/types.d.ts b/types.d.ts index 18a33985..0df93509 100644 --- a/types.d.ts +++ b/types.d.ts @@ -14,6 +14,8 @@ declare namespace BLECentralPlugin { id: string; rssi: number; advertising: ArrayBuffer | any; + /* Android only */ + connectable?: boolean; state: PeripheralState; } From a9745cec5b8e0ce265332d25a88ea12b559a14d5 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 4 Nov 2023 10:38:54 +1100 Subject: [PATCH 53/86] 1.7.2 --- package.json | 2 +- plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 110c614f..6ae5d8d0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.7.1", + "version": "1.7.2", "description": "Bluetooth Low Energy (BLE) Central Plugin", "scripts": { "slimify": "node tools/make-slim-variant.mjs", diff --git a/plugin.xml b/plugin.xml index cfc3700e..385f99cf 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin From 056093ce8569ee6263c715cd0bb5084325311335 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 4 Nov 2023 11:16:01 +1100 Subject: [PATCH 54/86] Add build badges --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 37cd9fe7..3dae667a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Bluetooth Low Energy (BLE) Central Plugin for Apache Cordova -[![npm version](https://img.shields.io/npm/v/cordova-plugin-ble-central.svg?style=flat)](https://www.npmjs.com/package/cordova-plugin-ble-central) +[![npm version](https://img.shields.io/npm/v/cordova-plugin-ble-central.svg?style=flat)](https://www.npmjs.com/package/cordova-plugin-ble-central) ![maintained](https://img.shields.io/maintenance/yes/2023?style=flat-square) [![cordova builds](https://img.shields.io/github/actions/workflow/status/don/cordova-plugin-ble-central/.github/workflows/cordova.yml?branch=master&style=flat-square&label=cordova%20builds)](https://github.com/don/cordova-plugin-ble-central/actions/workflows/cordova.yml?query=branch%3Amaster) [![capacitor builds](https://img.shields.io/github/actions/workflow/status/don/cordova-plugin-ble-central/.github/workflows/capacitor.yaml?branch=master&style=flat-square&label=capacitor%20builds)](https://github.com/don/cordova-plugin-ble-central/actions/workflows/capacitor.yaml?query=branch%3Amaster) This plugin enables communication between a phone and Bluetooth Low Energy (BLE) peripherals. From 073c80dbc269341a4822663bde85d090e98d32c4 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 4 Nov 2023 10:29:43 +1000 Subject: [PATCH 55/86] Clarify license as Apache-2.0 with full text --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++ LICENSE.txt | 13 ---- README.md | 2 +- 3 files changed, 202 insertions(+), 14 deletions(-) create mode 100644 LICENSE delete mode 100644 LICENSE.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..6c777cfc --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2014-2020 Don Coleman + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index b715ce5a..00000000 --- a/LICENSE.txt +++ /dev/null @@ -1,13 +0,0 @@ -Copyright 2014-2020 Don Coleman - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/README.md b/README.md index 3dae667a..ee973f12 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Bluetooth Low Energy (BLE) Central Plugin for Apache Cordova -[![npm version](https://img.shields.io/npm/v/cordova-plugin-ble-central.svg?style=flat)](https://www.npmjs.com/package/cordova-plugin-ble-central) ![maintained](https://img.shields.io/maintenance/yes/2023?style=flat-square) [![cordova builds](https://img.shields.io/github/actions/workflow/status/don/cordova-plugin-ble-central/.github/workflows/cordova.yml?branch=master&style=flat-square&label=cordova%20builds)](https://github.com/don/cordova-plugin-ble-central/actions/workflows/cordova.yml?query=branch%3Amaster) [![capacitor builds](https://img.shields.io/github/actions/workflow/status/don/cordova-plugin-ble-central/.github/workflows/capacitor.yaml?branch=master&style=flat-square&label=capacitor%20builds)](https://github.com/don/cordova-plugin-ble-central/actions/workflows/capacitor.yaml?query=branch%3Amaster) +[![npm version](https://img.shields.io/npm/v/cordova-plugin-ble-central.svg?style=flat)](https://www.npmjs.com/package/cordova-plugin-ble-central) ![maintained](https://img.shields.io/maintenance/yes/2023?style=flat-square) [![cordova builds](https://img.shields.io/github/actions/workflow/status/don/cordova-plugin-ble-central/.github/workflows/cordova.yml?branch=master&style=flat-square&label=cordova%20builds)](https://github.com/don/cordova-plugin-ble-central/actions/workflows/cordova.yml?query=branch%3Amaster) [![capacitor builds](https://img.shields.io/github/actions/workflow/status/don/cordova-plugin-ble-central/.github/workflows/capacitor.yaml?branch=master&style=flat-square&label=capacitor%20builds)](https://github.com/don/cordova-plugin-ble-central/actions/workflows/capacitor.yaml?query=branch%3Amaster) [![license](https://img.shields.io/npm/l/cordova-plugin-ble-central.svg?style=flat)](https://www.npmjs.com/package/cordova-plugin-ble-central) This plugin enables communication between a phone and Bluetooth Low Energy (BLE) peripherals. From 1727fd1d305f3265a72debe51af81d515ec40a19 Mon Sep 17 00:00:00 2001 From: fudom <143608856+fudom@users.noreply.github.com> Date: Wed, 24 Jan 2024 03:54:06 +0100 Subject: [PATCH 56/86] Update and rename CHANGES.txt to CHANGELOG.md (#1001) --- CHANGELOG.md | 323 +++++++++++++++++++++++++++++++++++++++++++++++++++ CHANGES.txt | 274 ------------------------------------------- 2 files changed, 323 insertions(+), 274 deletions(-) create mode 100644 CHANGELOG.md delete mode 100644 CHANGES.txt diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..15035c22 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,323 @@ +## 1.7.2 + +- Android: add isConnectable Property for API26+ (O) #823 (#993) thanks @Gargamil + +## 1.7.1 + +- Android: Add forceScanFilter option for Android (#989, #987) thanks @younesspotmaster + +## 1.7.0 + +- iOS: Prevent API warnings on iOS if Bluetooth is disabled during timed scan +- iOS: Ensure scan timeout is cleared and reset when new scans are started +- Android: Ensure scan timeout is cleared and reset when new scans are started +- Standardise all scans to go via scanWithOptions +- Add duration flag (in seconds) to scan with options +- CI: Create GitHub Cordova & Capacitor Actions (#969) +- Documentation: update readme with info for advertisement parsing module +- Documentation: Detail capacitor installation instructions + +## 1.6.3 + +- Android: Implement manual bond control on #605 #843 + +## 1.6.2 + +- Android: Don't leak refreshDeviceCache callback +- Android: More thoroughly clean up callbacks on device disconnect #954 +- Android: Don't disconnect if refreshDeviceCache fails + +## 1.6.1 + +No changes - republish only + +## 1.6.0 + +- Android: Fix enable bluetooth permissions on Android 12+ #940 #941 Thanks @samvimes01 +- Android: Clear up time-based stopScan when new scan is started #902 +- Android: Report if Bluetooth is disabled when scanning/connecting #826 +- Android: Restore refreshDeviceCache to 1.3.X functionality (related to #936) +- Android: Prevent various null ref exceptions (#936, #901 #871, #773, #698) +- Android: Document advanced android scan options +- Android: Improve Android 11 background permissions request ordering +- iOS: Make connect failure errors on iOS more concise #933 Thanks @doug-a-brunner + +## 1.5.2 + +- Android: Exclude ACCESS_BACKGROUND_LOCATION from plugin.xml to avoid complications with Capacitor #928 + +## 1.5.1 + +- Android: Request BLUETOOTH_CONNECT on Android 12+ when device name is requested #924 +- Android: Avoid Null reference exceptions in service discovery callbacks +- Documentation & type fixes + +## 1.5.0 + +- Android: Add support for target SDK version 31 (Android 12) #923 #879 #875 Thanks @LDLCLid3 +- Add L2CAP support (minimum iOS 11 & Android 10 required) for connection-oriented Bluetooth streams #812 +- Add ble.withPromises.startNotification variant to indicate when notifications are successfully subscribed #903 #95 + +## 1.4.4 + +- Browser: Add support via navigator.Bluetooth #907 #231 Thanks @joeferner +- iOS: More explicitly handle edge cases when starting/stopping notifications on iOS #577 #893 +- iOS: Improve ble.write handling when given non-ArrayBuffer types #897 #900 +- Android: Report peripherals as disconnected when BLE is turned off #894 #896 +- Android: Properly dispose of connected gatts on plugin reset #825 #845 + +## 1.4.3 + +- iOS: Make BLUETOOTH_RESTORE_STATE variable optional +- Android Synchronise access to the gatt during cleanup to avoid null ref errors #799 +- Android: Ensure scan settings are correctly applied when permissions are requested +- Android: Correctly set match mode rather than callback type for scanWithOptions +- Android: Align "Not connected" message with iOS #784 + +## 1.4.2 + +- Add typescript definitions for all methods +- Add location state notifications on Android (similar to Bluetooth state notifications) +- Add support for iOS state restoration. This is opt-in, see README for usage details #717 +- Turn API misuse warnings on iOS into reported errors to prevent dead scans #828 + +## 1.4.1 + +- Add additional options to startScanWithOptions() on Android #835 Thanks @helllamer +- Fix some instances where the Android command loop locks up #847 #830 Thanks @doug-a-brunner +- Improve documentation around requestConnectionPriority usage #877 + +## 1.4.0 + +- Android has new plugin variable ACCESS_BACKGROUND_LOCATION enable or disable background scanning permissions. Defaults to false. #844 #821 #870 Thanks @marioshtika + +## 1.3.1 + +- Android updated to BluetoothLeScanner removing deprecated LeScanCallback #796 Thanks @pentabarf +- Android updated to work with android-cordova@8 and android-cordova@9 #819 +- iOS has new plugin variable IOS_INIT_ON_LOAD to delay plugin initialization. Defaults to false. #739 #769 Thanks @jospete + +## 1.3.0 + +- Add new location permssions Android 10 (API29) #771 Thanks @tiagoblackcode, @subratpalhar92 & @QuentinFarizon + +## 1.2.5 + +- Add setPin method for Android #718 Thanks @untilbit +- Give the user feedback after an MTU size request #715 Thanks @agren +- Add requestConnectionPriority for Android #714 #713 Thanks @agren @vamshik113 + +## 1.2.4 + +- Add sequence numbers to notifications on Android to handle out of order notifications. Android #625 #656 Thanks @timburke +- Location services is now a warning on Android. Add isLocationEnabled() #607 #633 Thanks @doug-a-brunner +- Use printf of cordova for LOG, reuse pattern, improve loop #647 Thanks @ChristianLutz +- Add queueCleanup method to API #695 Thanks @untilbit +- Fix null pointer in onLeScan #500 Thanks @fjms +- Remove cordova-plugin-compat #705 #617 + +## 1.2.3 + +- Fix iOS crash when advertising data contains kCBAdvDataLeBluetoothDeviceAddress #685 #697 Thanks @jospete +- Fix problem with multiple devices and notifications when one device disconnected #674 Thanks @mandrade-digmap +- Add NSBluetoothAlwaysUsageDescription for iOS13 #704 #700 Thanks @favnec5 + +## 1.2.2 + +- Remove lambda from Peripheral.java to maintain 1.6 source compatibility #602 +- Remove showBluetoothSettings for iOS #603 + +## 1.2.1 + +- Fix EXC_BAD_ACCESS on iOS #389 Thanks @claudiovolpato +- Return error if bad device id is passed to disconnect #410 +- Better error message when location permission is denied Android #218 +- Scan returns an error if location services are disabled Android #527 +- Improve autoconnect for iOS #599 +- Add ble.refreshDeviceCache (Android) #587 Thanks @Domvel +- Add ble.bondedDevices (Android) +- Add ble.connectedPeripheralsWithServices and ble.peripheralsWithIdentifiers (iOS) + +## 1.2.0 + +- Added un-scanned Peripheral concept on Android #560 Thanks @doug-a-brunner +- Fixed failure to fire callbacks on Android when read or write in flight #561 Thanks @doug-a-brunner +- Fixed dangling promises when reconnecting Android #562 Thanks @doug-a-brunner +- Added error when starting a scan while another is running Android #565 Thanks @doug-a-brunner +- Request MTU Size on Android #568 Thanks @Domvel and @Algoritma +- Don't prompt user to enable Bluetooth on iOS CBCentralManagerOptionShowPowerAlertKey #580 #174 Thanks @H0rst and @cairin @michie +- Implement showBluetoothSettings on iOS #591 Thanks @cairinmichie +- Improve disconnect logic on Android #582 + +## 1.1.9 + +- iOS error #558 + +## 1.1.8 + +- Fix merge conflicts + +## 1.1.7 + +- Use same characteristic uuid with different service in iOS #349 Thanks @riccardodegan-geekcups +- ble.read() example #346 Thanks @ktemby +- Remove pending stopNotificationCallback for iOS #355 Thanks @legege +- Add missing `resolve` and `reject` callbacks to Promise wrapper #360 Thanks @aj-dev +- Fix documentation typo #371 Thanks @maxchu2021 +- Fix documentation typo #424 Thanks @ChanHyuk-Im +- Fix duplicate symbol when using with with cordova-plugin-ble-peripheral #373 Thanks @lucatorella +- Add admonition about using with beacons #413 Hugh Barnes (hughbris) +- Handle errors in didUpdateValueForCharacteristic #385 Thanks @soyelporras +- Fix spelling error in Android code # Thanks @doug-a-brunner +- cordova-plugin-compat deprecated #466 #483 thanks @ddugue +- Fix NullPointer exception on scan #504 +- Fixed deprecated iOS CBPeripheral RSSI calls and build warning #446 Thanks @doug-a-brunner +- Trapped commands that caused iOS API misuse warnings #450 Thanks @doug-a-brunner +- Fire callbacks on iOS when device is disconnecting #451 Thanks @doug-a-brunner +- Fixed NSInvalidArgumentException when 'undefined' passed to plugin cmds #452 Thanks @doug-a-brunner +- Better errors on Android, when trying to read or write to a non-existing service #486 Thanks @ddugue +- Add autoConnect support #499 Thanks @hypersolution1 + +## 1.1.4 + +- Prevents scan from removing connecting Peripherals on Android #315 & #341 Thanks @mebrunet +- Documentation fixes #330 Thanks @motla +- Documenation clarification about Location Services #318 Thanks @petrometro +- Ensure peripheral is connected for startNotification and stopNotification on Android #343 +- Error message for Android 4.3 devices that don't support BLE #263 Thanks @PeacePan +- Must call scan before connect. Update documentation #340 + +## 1.1.3 + +- NSBluetoothPeripheralUsageDescription #324 Thanks @timkim + +## 1.1.2 + +- Call connect failure callback for peripheral if user disables Bluetooth #264 +- Fix iOS problem with multiple keys in service data #288 Thanks @senator +- Add errorMessage to JSON object that is returned (to connect failure callback) when a peripheral disconnects +- Call gatt.disconnect() before gatt.close() to get problematic devices to disconnect #221, #254, #214 +- Include version of JavaScript API with promises #247 Thanks @kellycampbell +- stopNotification on Android writes DISABLE_NOTIFICATION_VALUE to the descriptor #225 Thanks @zjw1918 + +## 1.1.1 + +- Update advertising data in peripheral while scanning (Android) #253 + +## 1.1.0 + +- Add documentation about receiving notifications in the background on iOS #169 +- Add option to report duplicates during scan #172 Thanks @chris-armstrong +- Read RSSI value from an active BLE connection #171 Thanks @chris-armstrong +- Register for callbacks on Bluetooth state change #136 Thanks @rrharvey +- Fix example for write characteristic #195 Thanks @Wynout +- Fix documentation for write & writeWithoutResponse #193 Thanks @blakeparkinson +- Update CC2650 example #200 Thanks @jplourenco +- Connect peripheral with missing ble-flag (Android SDK 23) #226 Thanks @PeacePan + +## 1.0.6 + +- Fix compile error with Cordova 5.x #219 + +## 1.0.5 + +- Request Permissions for Android 6.0 (API level 23) #182 +- Update documentation for isEnabled #170 + +## 1.0.4 + +- Fix compile error with ios@4.0.1 #161 + +## 1.0.3 + +- Don't block UI thread on Android when starting scan #121 Thanks @kellycampbell +- Return characteristic even if properties don't match #132 #113 Thanks @kanayo +- StopNotification for Android fixes #51 +- Fix conflicts with the BluetoothSerial plugin #114 + +## 1.0.2 + +- Update plugin id for examples +- Fix npm keywords + +## 1.0.1 + +- Handle services that reuse UUIDs across characteristics #82 #94 Thanks @ggordan +- Disconnect cancels pending connections on iOS #93 Thanks @rrharvey +- Add dummy browser platform implementation for better PhoneGap developer app support #87 #90 Thanks @surajpindoria +- Replace notify in examples with startNotification #63 +- Stop notification from stacking on Android #71 Thanks @charlesalton +- Connect failure callback returns the peripheral #16 +- Better log message for unsupported iOS hardware #60 +- Update bluefruitle example to work with new versions of the hardware + +## 1.0.0 + +- Change plugin id cordova-plugin-ble-central +- Move to NPM #86 +- iOS 9 #62 Thanks Khashayar Pourdeilami + +## 0.1.9 + +- Add start of WP8 for PGDA + +## 0.1.8 + +- Remove SDK version from config.xml (user is responsible for adding) +- Add tests for plugin +- Fix BluetoothLE example for Adafruit nRF8001 #57 + +## 0.1.7 + +- Add showBluetoothSettings and enable for Android #43 +- Update documentation about UUIDs #38 + +## 0.1.6 + +- startNotification handles both notifications and indications + +## 0.1.5 + +- Add startScan and stopScan #40 +- Update to RFduino example + +## 0.1.4 + +- Change Android behavior for leScan without service list + +## 0.1.3 + +- Remove NO_RESULT on iOS fixes #32 + +## 0.1.2 + +- Ensure connect success callback is only called 1x on iOS #30 +- Rename notify to startNotification +- Add stopNotification (iOS only) + +## 0.1.1 + +- Return reason code when write fails on iOS #29 + +## 0.1.0 + +- Return advertising data in scan results #6, #7, #18 +- Connect success returns service, characteristic and descriptor info #6 +- iOS connectCallbackId is stored in Peripheral #17 +- Move plugin directory to top level for phonegap build compatibility #20 +- Rename writeCommand to writeWithoutResponse #21 +- Services callback latch is per peripheral #19 +- Connect callback is per peripheral #17 +- Fix iOS crash when scanning 2x #5 +- Add ble.isEnabled method #11 +- Add RedBearLab example +- Add BatteryService example + +## 0.0.2 + +- iOS - fix bug read callback was being called 2x +- iOS - fix bug write callback wasn't being called + +## 0.0.1 + +initial release diff --git a/CHANGES.txt b/CHANGES.txt deleted file mode 100644 index f7f1492d..00000000 --- a/CHANGES.txt +++ /dev/null @@ -1,274 +0,0 @@ -= 1.7.2 = -Android: add isConnectable Property for API26+ (O) #823 (#993) thanks @Gargamil - -= 1.7.1 = -Android: Add forceScanFilter option for Android (#989, #987) thanks younesspotmaster - -= 1.7.0 = -iOS: Prevent API warnings on iOS if Bluetooth is disabled during timed scan -iOS: Ensure scan timeout is cleared and reset when new scans are started -Android: Ensure scan timeout is cleared and reset when new scans are started -Standardise all scans to go via scanWithOptions -Add duration flag (in seconds) to scan with options -CI: Create GitHub Cordova & Capacitor Actions (#969) -Documentation: update readme with info for advertisement parsing module -Documentation: Detail capacitor installation instructions - -= 1.6.3 = -Android: Implement manual bond control on #605 #843 - -= 1.6.2 = -Android: Don't leak refreshDeviceCache callback -Android: More thoroughly clean up callbacks on device disconnect #954 -Android: Don't disconnect if refreshDeviceCache fails - -= 1.6.1 = -No changes - republish only - -= 1.6.0 = -Android: Fix enable bluetooth permissions on Android 12+ #940 #941 Thanks samvimes01 -Android: Clear up time-based stopScan when new scan is started #902 -Android: Report if Bluetooth is disabled when scanning/connecting #826 -Android: Restore refreshDeviceCache to 1.3.X functionality (related to #936) -Android: Prevent various null ref exceptions (#936, #901 #871, #773, #698) -Android: Document advanced android scan options -Android: Improve Android 11 background permissions request ordering -iOS: Make connect failure errors on iOS more concise #933 Thanks doug-a-brunner - -= 1.5.2 = -Android: Exclude ACCESS_BACKGROUND_LOCATION from plugin.xml to avoid complications with Capacitor #928 - -= 1.5.1 = -Android: Request BLUETOOTH_CONNECT on Android 12+ when device name is requested #924 -Android: Avoid Null reference exceptions in service discovery callbacks -Documentation & type fixes - -= 1.5.0 = -Android: Add support for target SDK version 31 (Android 12) #923 #879 #875 Thanks LDLCLid3 -Add L2CAP support (minimum iOS 11 & Android 10 required) for connection-oriented Bluetooth streams #812 -Add ble.withPromises.startNotification variant to indicate when notifications are successfully subscribed #903 #95 - -= 1.4.4 = -Browser: Add support via navigator.Bluetooth #907 #231 Thanks joeferner -iOS: More explicitly handle edge cases when starting/stopping notifications on iOS #577 #893 -iOS: Improve ble.write handling when given non-ArrayBuffer types #897 #900 -Android: Report peripherals as disconnected when BLE is turned off #894 #896 -Android: Properly dispose of connected gatts on plugin reset #825 #845 - -= 1.4.3 = -iOS: Make BLUETOOTH_RESTORE_STATE variable optional -Android Synchronise access to the gatt during cleanup to avoid null ref errors #799 -Android: Ensure scan settings are correctly applied when permissions are requested -Android: Correctly set match mode rather than callback type for scanWithOptions -Android: Align "Not connected" message with iOS #784 - -= 1.4.2 = -Add typescript definitions for all methods -Add location state notifications on Android (similar to Bluetooth state notifications) -Add support for iOS state restoration. This is opt-in, see README for usage details #717 -Turn API misuse warnings on iOS into reported errors to prevent dead scans #828 - -= 1.4.1 = -Add additional options to startScanWithOptions() on Android #835 Thanks helllamer -Fix some instances where the Android command loop locks up #847 #830 Thanks doug-a-brunner -Improve documentation around requestConnectionPriority usage #877 - -= 1.4.0 = -Android has new plugin variable ACCESS_BACKGROUND_LOCATION enable or disable background scanning permissions. Defaults to false. #844 #821 #870 Thanks marioshtika - -= 1.3.1 = -Android updated to BluetoothLeScanner removing deprecated LeScanCallback #796 Thanks pentabarf -Android updated to work with android-cordova@8 and android-cordova@9 #819 -iOS has new plugin variable IOS_INIT_ON_LOAD to delay plugin initialization. Defaults to false. #739 #769 Thanks josoete - -= 1.3.0 = -Add new location permssions Android 10 (API29) #771 Thanks tiagoblackcode, subratpalhar92, & QuentinFarizon - -= 1.2.5 = -Add setPin method for Android #718 Thanks untilbit -Give the user feedback after an MTU size request #715 Thanks agren -Add requestConnectionPriority for Android #714 #713 Thanks agren vamshik113 - -= 1.2.4 = -Add sequence numbers to notifications on Android to handle out of order notifications. Android #625 #656 Thanks timburke -Location services is now a warning on Android. Add isLocationEnabled() #607 #633 Thanks doug-a-brunner -Use printf of cordova for LOG, reuse pattern, improve loop #647 Thanks ChristianLutz -Add queueCleanup method to API #695 Thanks untilbit -Fix null pointer in onLeScan #500 Thanks fjms -Remove cordova-plugin-compat #705 #617 - -= 1.2.3 = -Fix iOS crash when advertising data contains kCBAdvDataLeBluetoothDeviceAddress #685 #697 Thanks jospete -Fix problem with multiple devices and notifications when one device disconnected #674 Thanks mandrade-digmap -Add NSBluetoothAlwaysUsageDescription for iOS13 #704 #700 Thanks favnec5 - -= 1.2.2 = -Remove lambda from Peripheral.java to maintain 1.6 source compatibility #602 -Remove showBluetoothSettings for iOS #603 - -= 1.2.1 = -Fix EXC_BAD_ACCESS on iOS #389 Thanks claudiovolpato -Return error if bad device id is passed to disconnect #410 -Better error message when location permission is denied Android #218 -Scan returns an error if location services are disabled Android #527 -Improve autoconnect for iOS #599 -add ble.refreshDeviceCache (Android) #587 Thanks Domvel -add ble.bondedDevices (Android) -add ble.connectedPeripheralsWithServices and ble.peripheralsWithIdentifiers (iOS) - -= 1.2.0 = -Added un-scanned Peripheral concept on Android #560 Thanks doug-a-brunner -Fixed failure to fire callbacks on Android when read or write in flight #561 Thanks doug-a-brunner -Fixed dangling promises when reconnecting Android #562 Thanks doug-a-brunner -Added error when starting a scan while another is running Android #565 Thanks doug-a-brunner -Request MTU Size on Android #568 Thanks Domvel and Algoritma -Don't prompt user to enable Bluetooth on iOS CBCentralManagerOptionShowPowerAlertKey #580 #174 Thanks H0rst and cairinmichie -Implement showBluetoothSettings on iOS #591 Thanks cairinmichie -Improve disconnect logic on Android #582 - -= 1.1.9 = -iOS error #558 - -= 1.1.8 = -Fix merge conflicts - -= 1.1.7 = -Use same characteristic uuid with different service in iOS #349 Thanks Riccardo Degan (riccardodegan-geekcups) -ble.read() example #346 Thanks Kelton Temby (ktemby) -Remove pending stopNotificationCallback for iOS #355 Thanks Georges-Etienne Legendre (legege) -add missing `resolve` and `reject` callbacks to Promise wrapper #360 Thanks Audrius Jakumavicius -(aj-dev) -Fix documentation typo #371 Thanks keanyc -Fix documentation typo #424 Thanks ChanHyuk-Im -Fix duplicate symbol when using with with cordova-plugin-ble-peripheral #373 Thanks Luca Torella (lucatorella) -Add admonition about using with beacons #413 Hugh Barnes (hughbris) -Handle errors in didUpdateValueForCharacteristic #385 Thanks soyelporras -Fix spelling error in Android code # Thanks doug-a-brunner -cordova-plugin-compat deprecated #466 #483 thanks ddugue -Fix NullPointer exception on scan #504 -Fixed deprecated iOS CBPeripheral RSSI calls and build warning #446 Thanks doug-a-brunner -Trapped commands that caused iOS API misuse warnings #450 Thanks doug-a-brunner -Fire callbacks on iOS when device is disconnecting #451 Thanks doug-a-brunner -Fixed NSInvalidArgumentException when 'undefined' passed to plugin cmds #452 Thanks doug-a-brunner -Better errors on Android, when trying to read or write to a non-existing service #486 Thanks ddugue -Add autoConnect support #499 Thanks hypersolution1 - -= 1.1.4 = -Prevents scan from removing connecting Peripherals on Android #315 & #341 Thanks mebrunet -Documentation fixes #330 Thanks motla -Documenation clarification about Location Services #318 Thanks petrometro -Ensure peripheral is connected for startNotification and stopNotification on Android #343 -Error message for Android 4.3 devices that don't support BLE #263 Thanks PeacePan -Must call scan before connect. Update documentation #340 - -= 1.1.3 = -NSBluetoothPeripheralUsageDescription #324 Thanks Tim Kim - -= 1.1.2 = -Call connect failure callback for peripheral if user disables Bluetooth #264 -Fix iOS problem with multiple keys in service data #288 Thanks Lebbeous Fogle-Weekley -Add errorMessage to JSON object that is returned (to connect failure callback) when a peripheral disconnects -Call gatt.disconnect() before gatt.close() to get problematic devices to disconnect #221, #254, #214 -Include version of JavaScript API with promises #247 Thanks Kelly Campbell -stopNotification on Android writes DISABLE_NOTIFICATION_VALUE to the descriptor #225 Thanks zjw1918 - -= 1.1.1 = -Update advertising data in peripheral while scanning (Android) #253 - -= 1.1.0 = -Add documentation about receiving notifications in the background on iOS #169 -Add option to report duplicates during scan #172 Thanks Chris Armstrong -Read RSSI value from an active BLE connection #171 Thanks Chris Armstrong -Register for callbacks on Bluetooth state change #136 Thanks Ryan Harvey -Fix example for write characteristic #195 Thanks Wynout van der Veer -Fix documentation for write & writeWithoutResponse #193 Thanks Blake Parkinson -Update CC2650 example #200 Thanks jplourenco -Connect peripheral with missing ble-flag (Android SDK 23) #226 Thanks PeacePan - -= 1.0.6 = -Fix compile error with Cordova 5.x #219 - -= 1.0.5 = -Request Permissions for Android 6.0 (API level 23) #182 -Update documentation for isEnabled #170 - -= 1.0.4 = -Fix compile error with ios@4.0.1 #161 - -= 1.0.3 = -Don't block UI thread on Android when starting scan #121 Thanks Kelly Campbell -Return characteristic even if properties don't match #132 #113 Thanks kanayo -StopNotification for Android fixes #51 -Fix conflicts with the BluetoothSerial plugin #114 - -= 1.0.2 = -Update plugin id for examples -Fix npm keywords - -= 1.0.1 = -Handle services that reuse UUIDs across characteristics #82 #94 Thanks ggordan -Disconnect cancels pending connections on iOS #93 Thanks rrharvey -Add dummy browser platform implementation for better PhoneGap developer app support #87 #90 Thanks surajpindoria -Replace notify in examples with startNotification #63 -Stop notification from stacking on Android #71 Thanks charlesalton -Connect failure callback returns the peripheral #16 -Better log message for unsupported iOS hardware #60 -Update bluefruitle example to work with new versions of the hardware - -= 1.0.0 = -Change plugin id cordova-plugin-ble-central -Move to NPM #86 -iOS 9 #62 Thanks Khashayar Pourdeilami - -= 0.1.9 = -Add start of WP8 for PGDA - -= 0.1.8 = -Remove SDK version from config.xml (user is responsible for adding) -Add tests for plugin -Fix BluetoothLE example for Adafruit nRF8001 #57 - -= 0.1.7 = -Add showBluetoothSettings and enable for Android #43 -Update documentation about UUIDs #38 - -= 0.1.6 = -startNotification handles both notifications and indications - -= 0.1.5 = -add startScan and stopScan #40 -update to RFduino example - -= 0.1.4 = -Change Android behavior for leScan without service list - -= 0.1.3 = -Remove NO_RESULT on iOS fixes #32 - -= 0.1.2 = -Ensure connect success callback is only called 1x on iOS #30 -Rename notify to startNotification -Add stopNotification (iOS only) - -= 0.1.1 = -Return reason code when write fails on iOS #29 - -= 0.1.0 = -Return advertising data in scan results #6, #7, #18 -Connect success returns service, characteristic and descriptor info #6 -iOS connectCallbackId is stored in Peripheral #17 -Move plugin directory to top level for phonegap build compatibility #20 -Rename writeCommand to writeWithoutResponse #21 -Services callback latch is per peripheral #19 -Connect callback is per peripheral #17 -Fix iOS crash when scanning 2x #5 -Add ble.isEnabled method #11 -Add RedBearLab example -Add BatteryService example - -= 0.0.2 = -iOS - fix bug read callback was being called 2x -iOS - fix bug write callback wasn't being called - -= 0.0.1 = -initial release From 592536782be18b708b36a9db0eed4a2391544ae8 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 13 Mar 2024 09:38:50 +1100 Subject: [PATCH 57/86] Update README.md - Clarify typescript support (#1007) - Turn on syntax highlighting for all code snippets --- README.md | 934 +++++++++++++++++++++++++++++------------------------- 1 file changed, 505 insertions(+), 429 deletions(-) diff --git a/README.md b/README.md index ee973f12..2d1af573 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ See the [examples](https://github.com/don/cordova-plugin-ble-central/tree/master The plugin can be further configured via the cordova preferences section of the [capacitor config file](https://capacitorjs.com/docs/cordova/migrating-from-cordova-to-capacitor#cordova-plugin-preferences): -``` +```typescript const config = { ... cordova: { @@ -83,13 +83,21 @@ All variables can be modified after installation by updating the values in `pack - `ACCESS_BACKGROUND_LOCATION`: [**Android**] Tells the plugin to request the ACCESS_BACKGROUND_LOCATION permission on Android 10 & Android 11 in order for scanning to function when the app is not visible. +## TypeScript + +TypeScript definitions are provided by this plugin. These can be added to a file by adding a [triple-slash reference](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) at the top of any file using this plugin: + +```typescript +/// +``` + ## Android permission conflicts If you are having Android permissions conflicts with other plugins, try using the `slim` variant of the plugin instead with `cordova plugin add cordova-plugin-ble-central@slim`. This variant excludes all Android permissions, leaving it to the developer to ensure the right entries are added manually to the `AndroidManifest.xml` (see below for an example). To include the default set of permissions the plugin installs on Android SDK v31+, add the following snippet in your `config.xml` file, in the `` section: -``` +```xml @@ -146,19 +154,21 @@ For the best understanding about which permissions are needed for which combinat ## scan Scan and discover BLE peripherals. - - ble.scan(services, seconds, success, failure); - +```javascript +ble.scan(services, seconds, success, failure); +``` ### Description Function `scan` scans for BLE devices. The success callback is called each time a peripheral is discovered. Scanning automatically stops after the specified number of seconds. - { - "name": "TI SensorTag", - "id": "BD922605-1B07-4D55-8D09-B66653E51BBA", - "rssi": -79, - "advertising": /* ArrayBuffer or map */ - } +```json +{ + "name": "TI SensorTag", + "id": "BD922605-1B07-4D55-8D09-B66653E51BBA", + "rssi": -79, + "advertising": /* ArrayBuffer or map */ +} +``` Advertising information format varies depending on your platform. See [Advertising Data](#advertising-data) for more information. @@ -176,27 +186,32 @@ Location Services must be enabled for Bluetooth scanning. If location services a - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example - - ble.scan([], 5, function(device) { - console.log(JSON.stringify(device)); - }, failure); +```javascript +ble.scan([], 5, function(device) { + console.log(JSON.stringify(device)); +}, failure); +``` ## startScan Scan and discover BLE peripherals. - ble.startScan(services, success, failure); +```javascript +ble.startScan(services, success, failure); +``` ### Description Function `startScan` scans for BLE devices. The success callback is called each time a peripheral is discovered. Scanning will continue until `stopScan` is called. - { - "name": "TI SensorTag", - "id": "BD922605-1B07-4D55-8D09-B66653E51BBA", - "rssi": -79, - "advertising": /* ArrayBuffer or map */ - } +```json +{ + "name": "TI SensorTag", + "id": "BD922605-1B07-4D55-8D09-B66653E51BBA", + "rssi": -79, + "advertising": /* ArrayBuffer or map */ +} +``` Advertising information format varies depending on your platform. See [Advertising Data](#advertising-data) for more information. @@ -209,33 +224,38 @@ See the [location permission notes](#location-permission-notes) above for inform - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example - - ble.startScan([], function(device) { - console.log(JSON.stringify(device)); - }, failure); - - setTimeout(ble.stopScan, - 5000, - function() { console.log("Scan complete"); }, - function() { console.log("stopScan failed"); } - ); +```javascript +ble.startScan([], function(device) { + console.log(JSON.stringify(device)); +}, failure); + +setTimeout(ble.stopScan, + 5000, + function() { console.log("Scan complete"); }, + function() { console.log("stopScan failed"); } +); +``` ## startScanWithOptions Scan and discover BLE peripherals, specifying scan options. - ble.startScanWithOptions(services, options, success, failure); +```javascript +ble.startScanWithOptions(services, options, success, failure); +``` ### Description Function `startScanWithOptions` scans for BLE devices. It operates similarly to the `startScan` function, but allows you to specify extra options (like allowing duplicate device reports). The success callback is called each time a peripheral is discovered. Scanning will continue until `stopScan` is called. - { - "name": "TI SensorTag", - "id": "BD922605-1B07-4D55-8D09-B66653E51BBA", - "rssi": -79, - "advertising": /* ArrayBuffer or map */ - } +```json +{ + "name": "TI SensorTag", + "id": "BD922605-1B07-4D55-8D09-B66653E51BBA", + "rssi": -79, + "advertising": /* ArrayBuffer or map */ +} +``` Advertising information format varies depending on your platform. See [Advertising Data](#advertising-data) for more information. @@ -264,27 +284,29 @@ See the [location permission notes](#location-permission-notes) above for inform - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example +```javascript +ble.startScanWithOptions([], + { reportDuplicates: true } + function(device) { + console.log(JSON.stringify(device)); + }, + failure); - ble.startScanWithOptions([], - { reportDuplicates: true } - function(device) { - console.log(JSON.stringify(device)); - }, - failure); - - setTimeout(ble.stopScan, - 5000, - function() { console.log("Scan complete"); }, - function() { console.log("stopScan failed"); } - ); +setTimeout(ble.stopScan, + 5000, + function() { console.log("Scan complete"); }, + function() { console.log("stopScan failed"); } +); +``` ## stopScan Stop scanning for BLE peripherals. - - ble.stopScan(success, failure); - // Or using await with promises - await ble.withPromises.stopScan() +```javascript +ble.stopScan(success, failure); +// Or using await with promises +await ble.withPromises.stopScan() +``` ### Description @@ -296,33 +318,34 @@ Function `stopScan` stops scanning for BLE devices. - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example - - ble.startScan([], function(device) { - console.log(JSON.stringify(device)); - }, failure); - - setTimeout(ble.stopScan, - 5000, +```javascript +ble.startScan([], function(device) { + console.log(JSON.stringify(device)); +}, failure); + +setTimeout(ble.stopScan, + 5000, + function() { console.log("Scan complete"); }, + function() { console.log("stopScan failed"); } +); + +/* Alternate syntax +setTimeout(function() { + ble.stopScan( function() { console.log("Scan complete"); }, function() { console.log("stopScan failed"); } ); - - /* Alternate syntax - setTimeout(function() { - ble.stopScan( - function() { console.log("Scan complete"); }, - function() { console.log("stopScan failed"); } - ); - }, 5000); - */ - +}, 5000); +*/ +``` ## setPin Set device pin - - ble.setPin(pin, [success], [failure]); - // Or using await with promises - await ble.withPromises.setPin(pin) +```javascript +ble.setPin(pin, [success], [failure]); +// Or using await with promises +await ble.withPromises.setPin(pin) +``` ### Description @@ -337,11 +360,12 @@ Function `setPin` sets the pin when device requires it. ## bond Initiate a bond with a remote device - - ble.bond(device_id, [success], [failure], [options]); - // Or using await with promises - await ble.withPromises.bond(device_id); - await ble.withPromises.bond(device_id, { usePairingDialog: true }); +```javascript +ble.bond(device_id, [success], [failure], [options]); +// Or using await with promises +await ble.withPromises.bond(device_id); +await ble.withPromises.bond(device_id, { usePairingDialog: true }); +``` ### Description @@ -363,10 +387,11 @@ bonding is complete. The bonding process may prompt the user to confirm the pair ## unbond Remove a bond for a remote device - - ble.unbond(device_id, [success], [failure]); - // Or using await with promises - await ble.withPromises.unbond(device_id); +```javascript +ble.unbond(device_id, [success], [failure]); +// Or using await with promises +await ble.withPromises.unbond(device_id); +``` ### Description @@ -386,10 +411,11 @@ bond has been removed. ## readBondState Get the bond state of a device as a string - - ble.readBondState(device_id, [success], [failure]); - // Or using await with promises - const bondState = await ble.withPromises.readBondState(device_id); +```javascript +ble.readBondState(device_id, [success], [failure]); +// Or using await with promises +const bondState = await ble.withPromises.readBondState(device_id); +``` ### Description @@ -414,8 +440,9 @@ Function `readBondState` retrieves the current bond state of the device. ## connect Connect to a peripheral. - - ble.connect(device_id, connectCallback, disconnectCallback); +```javascript +ble.connect(device_id, connectCallback, disconnectCallback); +``` ### Description @@ -438,8 +465,9 @@ For iOS, the plugin needs to know about any device UUID before calling connect. ## autoConnect Establish an automatic connection to a peripheral. - - ble.autoConnect(device_id, connectCallback, disconnectCallback); +```javascript +ble.autoConnect(device_id, connectCallback, disconnectCallback); +``` ### Description @@ -462,10 +490,11 @@ See notes about [scanning before connecting](#scanning-before-connecting) ## disconnect Disconnect. - - ble.disconnect(device_id, [success], [failure]); - // Or using await with promises - await ble.withPromises.disconnect(device_id) +```javascript +ble.disconnect(device_id, [success], [failure]); +// Or using await with promises +await ble.withPromises.disconnect(device_id) +``` ### Description @@ -480,8 +509,9 @@ Function `disconnect` disconnects the selected device. ## requestMtu requestMtu - - ble.requestMtu(device_id, mtu, [success], [failure]); +```javascript +ble.requestMtu(device_id, mtu, [success], [failure]); +``` ### Description @@ -501,23 +531,25 @@ The resulting MTU size is sent to the success callback. The requested and result - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example - - ble.requestMtu(device_id, new_mtu, - function(mtu){ - alert("MTU set to: " + mtu); - }, - function(failure){ - alert("Failed to request MTU."); - } - ); +```javascript +ble.requestMtu(device_id, new_mtu, + function(mtu){ + alert("MTU set to: " + mtu); + }, + function(failure){ + alert("Failed to request MTU."); + } +); +``` ## requestConnectionPriority requestConnectionPriority - - ble.requestConnectionPriority(device_id, priority, [success], [failure]); - // Or using await with promises - await ble.withPromises.requestConnectionPriority(device_id, priority) +```javascript +ble.requestConnectionPriority(device_id, priority, [success], [failure]); +// Or using await with promises +await ble.withPromises.requestConnectionPriority(device_id, priority) +``` ### Description @@ -541,21 +573,23 @@ Connection priority can be one of: - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example - - ble.requestConnectionPriority(device_id, "high", - function() { - alert("success"); - }, - function(failure){ - alert("Failed to request connection priority: " + failure); - } - ); +```javascript +ble.requestConnectionPriority(device_id, "high", + function() { + alert("success"); + }, + function(failure){ + alert("Failed to request connection priority: " + failure); + } +); +``` ## refreshDeviceCache refreshDeviceCache - - ble.refreshDeviceCache(deviceId, timeoutMillis, [success], [failure]); +```javascript +ble.refreshDeviceCache(deviceId, timeoutMillis, [success], [failure]); +``` ### Description @@ -579,10 +613,11 @@ _NOTE_ Since this uses an undocumented API it's not guaranteed to work. ## read Reads the value of a characteristic. - - ble.read(device_id, service_uuid, characteristic_uuid, success, failure); - // Or using await with promises - const data = await ble.withPromises.read(device_id, service_uuid, characteristic_uuid) +```javascript +ble.read(device_id, service_uuid, characteristic_uuid, success, failure); +// Or using await with promises +const data = await ble.withPromises.read(device_id, service_uuid, characteristic_uuid) +``` ### Description @@ -601,25 +636,27 @@ Raw data is passed from native code to the callback as an [ArrayBuffer](#typed-a ### Quick Example Retrieves an [ArrayBuffer](#typed-arrays) when reading data. - - // read data from a characteristic, do something with output data - ble.read(device_id, service_uuid, characteristic_uuid, - function(data){ - console.log("Hooray we have data"+JSON.stringify(data)); - alert("Successfully read data from device."+JSON.stringify(data)); - }, - function(failure){ - alert("Failed to read characteristic from device."); - } - ); +```javascript +// read data from a characteristic, do something with output data +ble.read(device_id, service_uuid, characteristic_uuid, + function(data){ + console.log("Hooray we have data"+JSON.stringify(data)); + alert("Successfully read data from device."+JSON.stringify(data)); + }, + function(failure){ + alert("Failed to read characteristic from device."); + } +); +``` ## write Writes data to a characteristic. - - ble.write(device_id, service_uuid, characteristic_uuid, data, success, failure); - // Or using await with promises - await ble.withPromises.write(device_id, service_uuid, characteristic_uuid, data) +```javascript +ble.write(device_id, service_uuid, characteristic_uuid, data, success, failure); +// Or using await with promises +await ble.withPromises.write(device_id, service_uuid, characteristic_uuid, data) +``` ### Description @@ -637,31 +674,33 @@ Function `write` writes data to a characteristic. ### Quick Example Use an [ArrayBuffer](#typed-arrays) when writing data. - - // send 1 byte to switch a light on - var data = new Uint8Array(1); - data[0] = 1; - ble.write(device_id, "FF10", "FF11", data.buffer, success, failure); - - // send a 3 byte value with RGB color - var data = new Uint8Array(3); - data[0] = 0xFF; // red - data[1] = 0x00; // green - data[2] = 0xFF; // blue - ble.write(device_id, "ccc0", "ccc1", data.buffer, success, failure); - - // send a 32 bit integer - var data = new Uint32Array(1); - data[0] = counterInput.value; - ble.write(device_id, SERVICE, CHARACTERISTIC, data.buffer, success, failure); +```javascript +// send 1 byte to switch a light on +var data = new Uint8Array(1); +data[0] = 1; +ble.write(device_id, "FF10", "FF11", data.buffer, success, failure); + +// send a 3 byte value with RGB color +var data = new Uint8Array(3); +data[0] = 0xFF; // red +data[1] = 0x00; // green +data[2] = 0xFF; // blue +ble.write(device_id, "ccc0", "ccc1", data.buffer, success, failure); + +// send a 32 bit integer +var data = new Uint32Array(1); +data[0] = counterInput.value; +ble.write(device_id, SERVICE, CHARACTERISTIC, data.buffer, success, failure); +``` ## writeWithoutResponse Writes data to a characteristic without confirmation from the peripheral. - - ble.writeWithoutResponse(device_id, service_uuid, characteristic_uuid, data, success, failure); - // Or using await with promises - await ble.withPromises.writeWithoutResponse(device_id, service_uuid, characteristic_uuid, data) +```javascript +ble.writeWithoutResponse(device_id, service_uuid, characteristic_uuid, data, success, failure); +// Or using await with promises +await ble.withPromises.writeWithoutResponse(device_id, service_uuid, characteristic_uuid, data) +``` ### Description @@ -679,11 +718,12 @@ Function `writeWithoutResponse` writes data to a characteristic without a respon ## startNotification Register to be notified when the value of a characteristic changes. - - ble.startNotification(device_id, service_uuid, characteristic_uuid, success, failure); - // Or using await with promises - // Note, initial promise resolves or rejects depending on whether the subscribe was successful - await ble.withPromises.startNotification(device_id, success, failure) +```javascript +ble.startNotification(device_id, service_uuid, characteristic_uuid, success, failure); +// Or using await with promises +// Note, initial promise resolves or rejects depending on whether the subscribe was successful +await ble.withPromises.startNotification(device_id, success, failure) +``` ### Description @@ -702,22 +742,24 @@ See [Background Notifications on iOS](#background-notifications-on-ios) - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example +```javascript +var onData = function(buffer) { + // Decode the ArrayBuffer into a typed Array based on the data you expect + var data = new Uint8Array(buffer); + alert("Button state changed to " + data[0]); +} - var onData = function(buffer) { - // Decode the ArrayBuffer into a typed Array based on the data you expect - var data = new Uint8Array(buffer); - alert("Button state changed to " + data[0]); - } - - ble.startNotification(device_id, "FFE0", "FFE1", onData, failure); +ble.startNotification(device_id, "FFE0", "FFE1", onData, failure); +``` ## stopNotification Stop being notified when the value of a characteristic changes. - - ble.stopNotification(device_id, service_uuid, characteristic_uuid, success, failure); - // Or using await with promises - await ble.withPromises.stopNotification(device_id) +```javascript +ble.stopNotification(device_id, service_uuid, characteristic_uuid, success, failure); +// Or using await with promises +await ble.withPromises.stopNotification(device_id) +``` ### Description @@ -734,10 +776,11 @@ Function `stopNotification` stops a previously registered notification callback. ## isConnected Reports the connection status. - - ble.isConnected(device_id, success, failure); - // Or using await with promises - await ble.withPromises.isConnected(device_id) +```javascript +ble.isConnected(device_id, success, failure); +// Or using await with promises +await ble.withPromises.isConnected(device_id) +``` ### Description @@ -752,22 +795,24 @@ NOTE that for many apps isConnected is unncessary. The app can track the connect - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example - - ble.isConnected( - 'FFCA0B09-CB1D-4DC0-A1EF-31AFD3EDFB53', - function() { - console.log("Peripheral is connected"); - }, - function() { - console.log("Peripheral is *not* connected"); - } - ); +```javascript +ble.isConnected( + 'FFCA0B09-CB1D-4DC0-A1EF-31AFD3EDFB53', + function() { + console.log("Peripheral is connected"); + }, + function() { + console.log("Peripheral is *not* connected"); + } +); +``` ## isEnabled Reports if bluetooth is enabled. - - ble.isEnabled(success, failure); +```javascript +ble.isEnabled(success, failure); +``` ### Description @@ -779,21 +824,23 @@ Function `isEnabled` calls the success callback when Bluetooth is enabled and th - **failure**: Error callback function, invoked when Bluetooth is disabled. ### Quick Example - - ble.isEnabled( - function() { - console.log("Bluetooth is enabled"); - }, - function() { - console.log("Bluetooth is *not* enabled"); - } - ); +```javascript +ble.isEnabled( + function() { + console.log("Bluetooth is enabled"); + }, + function() { + console.log("Bluetooth is *not* enabled"); + } +); +``` ## isLocationEnabled Reports if location services are enabled. - - ble.isLocationEnabled(success, failure); +```javascript +ble.isLocationEnabled(success, failure); +``` ### Description @@ -809,24 +856,26 @@ Function `isLocationEnabled` calls the success callback when location services a - **failure**: Error callback function, invoked when location services are disabled. ### Quick Example - - ble.isLocationEnabled( - function() { - console.log("location services are enabled"); - }, - function() { - console.log("location services are *not* enabled"); - } - ); +```javascript +ble.isLocationEnabled( + function() { + console.log("location services are enabled"); + }, + function() { + console.log("location services are *not* enabled"); + } +); +``` ## startLocationStateNotifications Registers to be notified when Location service state changes on the device. - - ble.startLocationStateNotifications(success, failure); - // Or using await with promises - // Note, initial promise resolves or rejects depending on whether the subscribe was successful - await ble.withPromises.startLocationStateNotifications(success, failure) +```javascript +ble.startLocationStateNotifications(success, failure); +// Or using await with promises +// Note, initial promise resolves or rejects depending on whether the subscribe was successful +await ble.withPromises.startLocationStateNotifications(success, failure) +``` ### Description @@ -842,20 +891,22 @@ Function `startLocationStateNotifications` calls the success callback when the L - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example - - ble.startLocationStateNotifications( - function(enabled) { - console.log("Location is " + enabled); - } - ); +```javascript +ble.startLocationStateNotifications( + function(enabled) { + console.log("Location is " + enabled); + } +); +``` ## stopLocationStateNotifications Stops state notifications. - - ble.stopLocationStateNotifications(success, failure); - // Or using await with promises - await ble.withPromises.stopLocationStateNotifications() +```javascript +ble.stopLocationStateNotifications(success, failure); +// Or using await with promises +await ble.withPromises.stopLocationStateNotifications() +``` ### Description @@ -868,11 +919,12 @@ Function `stopLocationStateNotifications` calls the success callback when Locati ## startStateNotifications Registers to be notified when Bluetooth state changes on the device. - - ble.startStateNotifications(success, failure); - // Or using await with promises - // Note, initial promise resolves or rejects depending on whether the subscribe was successful - await ble.withPromises.startStateNotifications(success, failure) +```javascript +ble.startStateNotifications(success, failure); +// Or using await with promises +// Note, initial promise resolves or rejects depending on whether the subscribe was successful +await ble.withPromises.startStateNotifications(success, failure) +``` ### Description @@ -900,20 +952,22 @@ Function `startStateNotifications` calls the success callback when the Bluetooth - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example - - ble.startStateNotifications( - function(state) { - console.log("Bluetooth is " + state); - } - ); +```javascript +ble.startStateNotifications( + function(state) { + console.log("Bluetooth is " + state); + } +); +``` ## stopStateNotifications Stops state notifications. - - ble.stopStateNotifications(success, failure); - // Or using await with promises - await ble.withPromises.stopStateNotifications() +```javascript +ble.stopStateNotifications(success, failure); +// Or using await with promises +await ble.withPromises.stopStateNotifications() +``` ### Description @@ -927,10 +981,11 @@ Function `stopStateNotifications` calls the success callback when Bluetooth stat ## showBluetoothSettings Show the Bluetooth settings on the device. - - ble.showBluetoothSettings(success, failure); - // Or using await with promises - await ble.withPromises.showBluetoothSettings() +```javascript +ble.showBluetoothSettings(success, failure); +// Or using await with promises +await ble.withPromises.showBluetoothSettings() +``` ### Description @@ -948,16 +1003,18 @@ Function `showBluetoothSettings` opens the Bluetooth settings for the operating - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example - - ble.showBluetoothSettings(); +```javascript +ble.showBluetoothSettings(); +``` ## enable Enable Bluetooth on the device. - - ble.enable(success, failure); - // Or using await with promises - ble.withPromises.enable(); +```javascript +ble.enable(success, failure); +// Or using await with promises +ble.withPromises.enable(); +``` ### Description @@ -977,21 +1034,23 @@ If `enable` is called when Bluetooth is already enabled, the user will not promp - **failure**: Error callback function, invoked if the user does not enabled Bluetooth. ### Quick Example - - ble.enable( - function() { - console.log("Bluetooth is enabled"); - }, - function() { - console.log("The user did *not* enable Bluetooth"); - } - ); +```javascript +ble.enable( + function() { + console.log("Bluetooth is enabled"); + }, + function() { + console.log("The user did *not* enable Bluetooth"); + } +); +``` ## readRSSI Read the RSSI value on the device connection. - - ble.readRSSI(device_id, success, failure); +```javascript +ble.readRSSI(device_id, success, failure); +``` ### Description @@ -1004,27 +1063,28 @@ Samples the RSSI value (a measure of signal strength) on the connection to a blu - **failure**: Error callback function, invoked if there is no current connection or if there is an error reading the RSSI. ### Quick Example - - var rssiSample; - ble.connect(device_id, - function(device) { - rssiSample = setInterval(function() { - ble.readRSSI(device_id, function(rssi) { - console.log('read RSSI',rssi,'with device', device_id); - }, function(err) { - console.error('unable to read RSSI',err); - clearInterval(rssiSample); - }) - }, 5000); - }, - function(err) { console.error('error connecting to device')} - ); - +```javascript +var rssiSample; +ble.connect(device_id, + function(device) { + rssiSample = setInterval(function() { + ble.readRSSI(device_id, function(rssi) { + console.log('read RSSI',rssi,'with device', device_id); + }, function(err) { + console.error('unable to read RSSI',err); + clearInterval(rssiSample); + }) + }, 5000); + }, + function(err) { console.error('error connecting to device')} +); +``` ## connectedPeripheralsWithServices Find the connected peripherals offering the listed service UUIDs. - - ble.connectedPeripheralsWithServices([service], success, failure); +```javascript +ble.connectedPeripheralsWithServices([service], success, failure); +``` ### Description @@ -1043,8 +1103,9 @@ Retreives a list of the peripherals (containing any of the specified services) c ## peripheralsWithIdentifiers Find the connected peripherals offering the listed peripheral UUIDs. - - ble.peripheralsWithIdentifiers([uuids], success, failure); +```javascript +ble.peripheralsWithIdentifiers([uuids], success, failure); +``` ### Description @@ -1063,10 +1124,11 @@ Sends a list of known peripherals by their identifiers to the success callback. ## restoredBluetoothState Retrieve the CBManager restoration state (if applicable) - - ble.restoredBluetoothState(success, failure); - // Or using await with promises - await ble.withPromises.restoredBluetoothState(); +```javascript +ble.restoredBluetoothState(success, failure); +// Or using await with promises +await ble.withPromises.restoredBluetoothState(); +``` ### Description @@ -1088,10 +1150,11 @@ If the application has no state restored, this will return an empty object. ## list Lists all peripherals discovered by the plugin due to scanning or connecting since app launch. - - ble.list(success, failure); - // Or using await with promises - await ble.withPromises.list(); +```javascript +ble.list(success, failure); +// Or using await with promises +await ble.withPromises.list(); +``` ### Description @@ -1109,10 +1172,11 @@ Sends a list of bonded low energy peripherals to the success callback. ## bondedDevices Find the bonded devices. - - ble.bondedDevices(success, failure); - // Or using await with promises - await ble.withPromises.bondedDevices(); +```javascript +ble.bondedDevices(success, failure); +// Or using await with promises +await ble.withPromises.bondedDevices(); +``` ### Description @@ -1130,16 +1194,18 @@ Sends a list of bonded low energy peripherals to the success callback. ## l2cap.open Open an L2CAP channel with a connected peripheral. The PSM is assigned by the peripheral, or possibly defined by the Bluetooth standard. - - ble.l2cap.open(device_id, psm, connectCallback, disconnectCallback); - // Or using await with promises - await ble.withPromises.l2cap.open(device_id, psm, disconnectCallback); +```javascript +ble.l2cap.open(device_id, psm, connectCallback, disconnectCallback); +// Or using await with promises +await ble.withPromises.l2cap.open(device_id, psm, disconnectCallback); +``` Android supports additional arguments in the psm flag to select whether the L2CAP channel is insecure or secure (iOS does this automatically): - - ble.l2cap.open(device_id, { psm: psm, secureChannel: true }, connectCallback, disconnectCallback); - // Or using await with promises - await ble.withPromises.l2cap.open(device_id, { psm: psm, secureChannel: true }, disconnectCallback); +```javascript +ble.l2cap.open(device_id, { psm: psm, secureChannel: true }, connectCallback, disconnectCallback); +// Or using await with promises +await ble.withPromises.l2cap.open(device_id, { psm: psm, secureChannel: true }, disconnectCallback); +``` ### Description @@ -1162,10 +1228,11 @@ The PSM (protocol/service multiplexer) is specified by the peripheral when it op ## l2cap.close Close an L2CAP channel. - - ble.l2cap.close(device_id, psm, success, failure); - // Or using await with promises - await ble.withPromises.l2cap.close(device_id, psm); +```javascript +ble.l2cap.close(device_id, psm, success, failure); +// Or using await with promises +await ble.withPromises.l2cap.close(device_id, psm); +``` ### Description @@ -1186,8 +1253,9 @@ Closes an open L2CAP channel with the selected device. All pending reads and wri ## l2cap.receiveData Receive data from an L2CAP channel. - - ble.l2cap.receiveData(device_id, psm, dataCallback); +```javascript +ble.l2cap.receiveData(device_id, psm, dataCallback); +``` ### Description @@ -1207,10 +1275,11 @@ Sets the function to be called whenever bytes are received on the L2CAP channel. ## l2cap.write Write data to an L2CAP channel. - - ble.l2cap.write(device_id, psm, data, success, failure); - // Or using await with promises - await ble.withPromises.l2cap.write(device_id, psm, data); +```javascript +ble.l2cap.write(device_id, psm, data, success, failure); +// Or using await with promises +await ble.withPromises.l2cap.write(device_id, psm, data); +``` ### Description @@ -1232,65 +1301,67 @@ Writes all data to an open L2CAP channel. If the data exceeds the available spac # Peripheral Data Peripheral Data is passed to the success callback when scanning and connecting. Limited data is passed when scanning. - - { - "name": "Battery Demo", - "id": "20:FF:D0:FF:D1:C0", - "advertising": [2,1,6,3,3,15,24,8,9,66,97,116,116,101,114,121], - "rssi": -55 - } +```json +{ + "name": "Battery Demo", + "id": "20:FF:D0:FF:D1:C0", + "advertising": [2,1,6,3,3,15,24,8,9,66,97,116,116,101,114,121], + "rssi": -55 +} +``` After connecting, the peripheral object also includes service, characteristic and descriptor information. - - { - "name": "Battery Demo", - "id": "20:FF:D0:FF:D1:C0", - "advertising": [2,1,6,3,3,15,24,8,9,66,97,116,116,101,114,121], - "rssi": -55, - "services": [ - "1800", - "1801", - "180f" - ], - "characteristics": [ - { - "service": "1800", - "characteristic": "2a00", - "properties": [ - "Read" - ] - }, - { - "service": "1800", - "characteristic": "2a01", - "properties": [ - "Read" - ] - }, - { - "service": "1801", - "characteristic": "2a05", - "properties": [ - "Read" - ] - }, - { - "service": "180f", - "characteristic": "2a19", - "properties": [ - "Read" - ], - "descriptors": [ - { - "uuid": "2901" - }, - { - "uuid": "2904" - } - ] - } - ] - } +```json +{ + "name": "Battery Demo", + "id": "20:FF:D0:FF:D1:C0", + "advertising": [2,1,6,3,3,15,24,8,9,66,97,116,116,101,114,121], + "rssi": -55, + "services": [ + "1800", + "1801", + "180f" + ], + "characteristics": [ + { + "service": "1800", + "characteristic": "2a00", + "properties": [ + "Read" + ] + }, + { + "service": "1800", + "characteristic": "2a01", + "properties": [ + "Read" + ] + }, + { + "service": "1801", + "characteristic": "2a05", + "properties": [ + "Read" + ] + }, + { + "service": "180f", + "characteristic": "2a19", + "properties": [ + "Read" + ], + "descriptors": [ + { + "uuid": "2901" + }, + { + "uuid": "2904" + } + ] + } + ] +} +``` # Advertising Data @@ -1302,53 +1373,56 @@ To get consistent advertising data payloads across platforms, you can use the [ble-central-advertisements](https://github.com/jospete/ble-central-advertisements) module. ## Android - - { - "name": "demo", - "id": "00:1A:7D:DA:71:13", - "advertising": ArrayBuffer, - "rssi": -37 - "connectable":"true" /*Only on Android >= API Level 26*/ - } +```json +{ + "name": "demo", + "id": "00:1A:7D:DA:71:13", + "advertising": ArrayBuffer, + "rssi": -37 + "connectable":"true" /*Only on Android >= API Level 26*/ +} +``` Convert the advertising info to a Uint8Array for processing. `var adData = new Uint8Array(peripheral.advertising)`. You application is responsible for parsing all the information out of the advertising ArrayBuffer using the [GAP type constants](https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile). For example to get the service data from the advertising info, I [parse the advertising info into a map](https://github.com/don/ITP-BluetoothLE/blob/887511c375b1ab2fbef3afe210d6a6b7db44cee9/phonegap/thermometer_v2/www/js/index.js#L18-L39) and then get the service data to retrieve a [characteristic value that is being broadcast](https://github.com/don/ITP-BluetoothLE/blob/887511c375b1ab2fbef3afe210d6a6b7db44cee9/phonegap/thermometer_v2/www/js/index.js#L93-L103). ## iOS Note that iOS uses the string value of the constants for the [Advertisement Data Retrieval Keys](https://developer.apple.com/library/ios/documentation/CoreBluetooth/Reference/CBCentralManagerDelegate_Protocol/index.html#//apple_ref/doc/constant_group/Advertisement_Data_Retrieval_Keys). This will likely change in the future. - - { - "name": "demo" - "id": "15B4F1C5-C9C0-4441-BD9F-1A7ED8F7A365", - "advertising": { - "kCBAdvDataLocalName": "demo", - "kCBAdvDataManufacturerData": {}, // arraybuffer data not shown - "kCBAdvDataServiceUUIDs": [ - "721b" - ], - "kCBAdvDataIsConnectable": true, - "kCBAdvDataServiceData": { - "BBB0": {} // arraybuffer data not shown - }, +```json +{ + "name": "demo" + "id": "15B4F1C5-C9C0-4441-BD9F-1A7ED8F7A365", + "advertising": { + "kCBAdvDataLocalName": "demo", + "kCBAdvDataManufacturerData": {}, // arraybuffer data not shown + "kCBAdvDataServiceUUIDs": [ + "721b" + ], + "kCBAdvDataIsConnectable": true, + "kCBAdvDataServiceData": { + "BBB0": {} // arraybuffer data not shown }, - "rssi": -61 - } - -Some of the values such as kCBAdvDataManufacturerData are ArrayBuffers. The data won't print out, but you can convert it to bytes using `new Uint8Array(peripheral.advertisting.kCBAdvDataManufacturerData)`. Your application is responsible for parsing and decoding any binary data such as kCBAdvDataManufacturerData or kCBAdvDataServiceData. - - function onDiscoverDevice(device) { - // log the device as JSON - console.log('Found Device', JSON.stringify(device, null, 2)); + }, + "rssi": -61 +} +``` - // on iOS, print the manufacturer data if it exists - if (device.advertising && device.advertising.kCBAdvDataManufacturerData) { - const mfgData = new Uint8Array(device.advertising.kCBAdvDataManufacturerData); - console.log('Manufacturer Data is', mfgData); - } +Some of the values such as `kCBAdvDataManufacturerData` are `ArrayBuffers`. The data won't print out, but you can convert it to bytes using `new Uint8Array(peripheral.advertisting.kCBAdvDataManufacturerData)`. Your application is responsible for parsing and decoding any binary data such as `kCBAdvDataManufacturerData` or `kCBAdvDataServiceData`. +```javascript +function onDiscoverDevice(device) { + // log the device as JSON + console.log('Found Device', JSON.stringify(device, null, 2)); + // on iOS, print the manufacturer data if it exists + if (device.advertising && device.advertising.kCBAdvDataManufacturerData) { + const mfgData = new Uint8Array(device.advertising.kCBAdvDataManufacturerData); + console.log('Manufacturer Data is', mfgData); } - ble.scan([], 5, onDiscoverDevice, onError); +} + +ble.scan([], 5, onDiscoverDevice, onError); +``` ## Browser @@ -1364,20 +1438,21 @@ Scan must be initiated from a user action (click, touch, etc). This plugin uses typed Arrays or ArrayBuffers for sending and receiving data. This means that you need convert your data to ArrayBuffers before sending and from ArrayBuffers when receiving. - - // ASCII only - function stringToBytes(string) { - var array = new Uint8Array(string.length); - for (var i = 0, l = string.length; i < l; i++) { - array[i] = string.charCodeAt(i); - } - return array.buffer; +```javascript +// ASCII only +function stringToBytes(string) { + var array = new Uint8Array(string.length); + for (var i = 0, l = string.length; i < l; i++) { + array[i] = string.charCodeAt(i); } + return array.buffer; +} - // ASCII only - function bytesToString(buffer) { - return String.fromCharCode.apply(null, new Uint8Array(buffer)); - } +// ASCII only +function bytesToString(buffer) { + return String.fromCharCode.apply(null, new Uint8Array(buffer)); +} +``` You can read more about typed arrays in these articles on [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays) and [HTML5 Rocks](http://www.html5rocks.com/en/tutorials/webgl/typed_arrays/). @@ -1394,14 +1469,15 @@ Android applications will continue to receive notification while the application iOS applications need additional configuration to allow Bluetooth to run in the background. Add a new section to config.xml - - - - - bluetooth-central - - - +```xml + + + + bluetooth-central + + + +``` See [ble-background](https://github.com/don/ble-background) example project for more details. From c805c5e6529a10ef4f3b67a9f10359095c4cdde5 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Fri, 15 Mar 2024 09:06:25 +1100 Subject: [PATCH 58/86] Update maintained status in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d1af573..fbc85d9a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Bluetooth Low Energy (BLE) Central Plugin for Apache Cordova -[![npm version](https://img.shields.io/npm/v/cordova-plugin-ble-central.svg?style=flat)](https://www.npmjs.com/package/cordova-plugin-ble-central) ![maintained](https://img.shields.io/maintenance/yes/2023?style=flat-square) [![cordova builds](https://img.shields.io/github/actions/workflow/status/don/cordova-plugin-ble-central/.github/workflows/cordova.yml?branch=master&style=flat-square&label=cordova%20builds)](https://github.com/don/cordova-plugin-ble-central/actions/workflows/cordova.yml?query=branch%3Amaster) [![capacitor builds](https://img.shields.io/github/actions/workflow/status/don/cordova-plugin-ble-central/.github/workflows/capacitor.yaml?branch=master&style=flat-square&label=capacitor%20builds)](https://github.com/don/cordova-plugin-ble-central/actions/workflows/capacitor.yaml?query=branch%3Amaster) [![license](https://img.shields.io/npm/l/cordova-plugin-ble-central.svg?style=flat)](https://www.npmjs.com/package/cordova-plugin-ble-central) +[![npm version](https://img.shields.io/npm/v/cordova-plugin-ble-central.svg?style=flat)](https://www.npmjs.com/package/cordova-plugin-ble-central) ![maintained](https://img.shields.io/maintenance/yes/2024?style=flat-square) [![cordova builds](https://img.shields.io/github/actions/workflow/status/don/cordova-plugin-ble-central/.github/workflows/cordova.yml?branch=master&style=flat-square&label=cordova%20builds)](https://github.com/don/cordova-plugin-ble-central/actions/workflows/cordova.yml?query=branch%3Amaster) [![capacitor builds](https://img.shields.io/github/actions/workflow/status/don/cordova-plugin-ble-central/.github/workflows/capacitor.yaml?branch=master&style=flat-square&label=capacitor%20builds)](https://github.com/don/cordova-plugin-ble-central/actions/workflows/capacitor.yaml?query=branch%3Amaster) [![license](https://img.shields.io/npm/l/cordova-plugin-ble-central.svg?style=flat)](https://www.npmjs.com/package/cordova-plugin-ble-central) This plugin enables communication between a phone and Bluetooth Low Energy (BLE) peripherals. From 6ffb5f7cb29da982b859a6b786d9f8d455a01dce Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 20 Mar 2024 11:50:18 +1100 Subject: [PATCH 59/86] Fix cordova CI (#1009) * Lock down ubuntu and android build tool versions --- .github/workflows/cordova.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/cordova.yml b/.github/workflows/cordova.yml index cbb30f00..5482da2f 100644 --- a/.github/workflows/cordova.yml +++ b/.github/workflows/cordova.yml @@ -11,37 +11,49 @@ jobs: cordova-android: name: android@${{ matrix.platform }} cordova@${{ matrix.cordova }} - runs-on: ubuntu-latest + runs-on: ubuntu-${{ matrix.ubuntu }} strategy: + fail-fast: false matrix: include: - jdk: 16 cordova: latest platform: latest node: 18.x + ubuntu: 22.04 + android-cmdline-tools-version: 9862592 - jdk: 11 cordova: 11 platform: 10 node: 14.x + ubuntu: 20.04 + android-cmdline-tools-version: 9862592 - jdk: 8 cordova: 10 platform: 10 node: 14.x + ubuntu: 20.04 + android-cmdline-tools-version: 8512546 steps: - uses: actions/checkout@v3 - - name: set up JDK ${{ matrix.jdk }} + - name: Setup JDK ${{ matrix.jdk }} uses: actions/setup-java@v3 with: java-version: ${{ matrix.jdk }} distribution: 'temurin' - - name: Use Node.js ${{ matrix.node }} + - name: Setup Node.js ${{ matrix.node }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node }} + - name: Setup Android SDK + uses: android-actions/setup-android@v3 + with: + cmdline-tools-version: ${{ matrix.android-cmdline-tools-version }} - name: Build test app run: | + sdkmanager "build-tools;30.0.3" npm install -g cordova@${{ matrix.cordova }} cordova create temp cd temp From 11026a0c96a0fc6bc8e3ada6338b3fe8fb17f1ef Mon Sep 17 00:00:00 2001 From: Klaus Sievers Date: Thu, 16 May 2024 15:40:02 +0200 Subject: [PATCH 60/86] fix: added mtu to success callback --- types.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types.d.ts b/types.d.ts index 0df93509..5a2f7d09 100644 --- a/types.d.ts +++ b/types.d.ts @@ -297,7 +297,7 @@ declare namespace BLECentralPlugin { /* May be used to request (on Android) a larger MTU size to be able to send more data at once [iOS] requestMtu is not supported on iOS. */ - requestMtu(device_id: string, mtu: number, success?: () => any, failure?: () => any): void; + requestMtu(device_id: string, mtu: number, success?: (mtu: number) => any, failure?: () => any): void; /* When Connecting to a peripheral android can request for the connection priority for faster communication. [iOS] requestConnectionPriority is not supported on iOS. */ From 7c5ea51ba31ce27bc71cdf0cec0c3cd2e3c0485b Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Fri, 17 May 2024 09:04:14 +1000 Subject: [PATCH 61/86] Prepare for 1.7.3 release - Expose mtu via promise interface - Update CHANGELOG - Reformat README --- CHANGELOG.md | 5 + README.md | 318 ++++++++++++++++++++++++++++++++------------------- types.d.ts | 4 + www/ble.js | 6 + 4 files changed, 217 insertions(+), 116 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15035c22..2517d684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.7.3 + +- Android: Expose Promise version of `requestMtu` for async/await +- Types: Added returned mtu to `requestMtu` success callback thanks #1012 @ksievers-irco + ## 1.7.2 - Android: add isConnectable Property for API26+ (O) #823 (#993) thanks @Gargamil diff --git a/README.md b/README.md index fbc85d9a..b2496d69 100644 --- a/README.md +++ b/README.md @@ -154,9 +154,11 @@ For the best understanding about which permissions are needed for which combinat ## scan Scan and discover BLE peripherals. + ```javascript ble.scan(services, seconds, success, failure); ``` + ### Description Function `scan` scans for BLE devices. The success callback is called each time a peripheral is discovered. Scanning automatically stops after the specified number of seconds. @@ -186,10 +188,16 @@ Location Services must be enabled for Bluetooth scanning. If location services a - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example + ```javascript -ble.scan([], 5, function(device) { - console.log(JSON.stringify(device)); -}, failure); +ble.scan( + [], + 5, + function (device) { + console.log(JSON.stringify(device)); + }, + failure +); ``` ## startScan @@ -224,15 +232,25 @@ See the [location permission notes](#location-permission-notes) above for inform - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example + ```javascript -ble.startScan([], function(device) { - console.log(JSON.stringify(device)); -}, failure); +ble.startScan( + [], + function (device) { + console.log(JSON.stringify(device)); + }, + failure +); -setTimeout(ble.stopScan, +setTimeout( + ble.stopScan, 5000, - function() { console.log("Scan complete"); }, - function() { console.log("stopScan failed"); } + function () { + console.log('Scan complete'); + }, + function () { + console.log('stopScan failed'); + } ); ``` @@ -284,6 +302,7 @@ See the [location permission notes](#location-permission-notes) above for inform - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example + ```javascript ble.startScanWithOptions([], { reportDuplicates: true } @@ -302,10 +321,11 @@ setTimeout(ble.stopScan, ## stopScan Stop scanning for BLE peripherals. + ```javascript ble.stopScan(success, failure); // Or using await with promises -await ble.withPromises.stopScan() +await ble.withPromises.stopScan(); ``` ### Description @@ -318,15 +338,25 @@ Function `stopScan` stops scanning for BLE devices. - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example + ```javascript -ble.startScan([], function(device) { - console.log(JSON.stringify(device)); -}, failure); +ble.startScan( + [], + function (device) { + console.log(JSON.stringify(device)); + }, + failure +); -setTimeout(ble.stopScan, +setTimeout( + ble.stopScan, 5000, - function() { console.log("Scan complete"); }, - function() { console.log("stopScan failed"); } + function () { + console.log('Scan complete'); + }, + function () { + console.log('stopScan failed'); + } ); /* Alternate syntax @@ -338,13 +368,15 @@ setTimeout(function() { }, 5000); */ ``` + ## setPin Set device pin + ```javascript ble.setPin(pin, [success], [failure]); // Or using await with promises -await ble.withPromises.setPin(pin) +await ble.withPromises.setPin(pin); ``` ### Description @@ -360,6 +392,7 @@ Function `setPin` sets the pin when device requires it. ## bond Initiate a bond with a remote device + ```javascript ble.bond(device_id, [success], [failure], [options]); // Or using await with promises @@ -387,6 +420,7 @@ bonding is complete. The bonding process may prompt the user to confirm the pair ## unbond Remove a bond for a remote device + ```javascript ble.unbond(device_id, [success], [failure]); // Or using await with promises @@ -411,6 +445,7 @@ bond has been removed. ## readBondState Get the bond state of a device as a string + ```javascript ble.readBondState(device_id, [success], [failure]); // Or using await with promises @@ -440,6 +475,7 @@ Function `readBondState` retrieves the current bond state of the device. ## connect Connect to a peripheral. + ```javascript ble.connect(device_id, connectCallback, disconnectCallback); ``` @@ -465,6 +501,7 @@ For iOS, the plugin needs to know about any device UUID before calling connect. ## autoConnect Establish an automatic connection to a peripheral. + ```javascript ble.autoConnect(device_id, connectCallback, disconnectCallback); ``` @@ -490,10 +527,11 @@ See notes about [scanning before connecting](#scanning-before-connecting) ## disconnect Disconnect. + ```javascript ble.disconnect(device_id, [success], [failure]); // Or using await with promises -await ble.withPromises.disconnect(device_id) +await ble.withPromises.disconnect(device_id); ``` ### Description @@ -509,8 +547,11 @@ Function `disconnect` disconnects the selected device. ## requestMtu requestMtu + ```javascript -ble.requestMtu(device_id, mtu, [success], [failure]); +ble.requestMtu(device_id, new_mtu, [success], [failure]); +// Or using await with promises +const actual_mtu = await ble.withPromises.requestMtu(device_id, new_mtu); ``` ### Description @@ -531,13 +572,16 @@ The resulting MTU size is sent to the success callback. The requested and result - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example + ```javascript -ble.requestMtu(device_id, new_mtu, - function(mtu){ - alert("MTU set to: " + mtu); +ble.requestMtu( + device_id, + new_mtu, + function (mtu) { + alert('MTU set to: ' + mtu); }, - function(failure){ - alert("Failed to request MTU."); + function (failure) { + alert('Failed to request MTU.'); } ); ``` @@ -545,10 +589,11 @@ ble.requestMtu(device_id, new_mtu, ## requestConnectionPriority requestConnectionPriority + ```javascript ble.requestConnectionPriority(device_id, priority, [success], [failure]); // Or using await with promises -await ble.withPromises.requestConnectionPriority(device_id, priority) +await ble.withPromises.requestConnectionPriority(device_id, priority); ``` ### Description @@ -573,13 +618,16 @@ Connection priority can be one of: - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example + ```javascript -ble.requestConnectionPriority(device_id, "high", - function() { - alert("success"); +ble.requestConnectionPriority( + device_id, + 'high', + function () { + alert('success'); }, - function(failure){ - alert("Failed to request connection priority: " + failure); + function (failure) { + alert('Failed to request connection priority: ' + failure); } ); ``` @@ -587,8 +635,9 @@ ble.requestConnectionPriority(device_id, "high", ## refreshDeviceCache refreshDeviceCache + ```javascript -ble.refreshDeviceCache(deviceId, timeoutMillis, [success], [failure]); +ble.refreshDeviceCache(deviceId, timeoutMillis, [success], [failure]); ``` ### Description @@ -613,10 +662,11 @@ _NOTE_ Since this uses an undocumented API it's not guaranteed to work. ## read Reads the value of a characteristic. + ```javascript ble.read(device_id, service_uuid, characteristic_uuid, success, failure); // Or using await with promises -const data = await ble.withPromises.read(device_id, service_uuid, characteristic_uuid) +const data = await ble.withPromises.read(device_id, service_uuid, characteristic_uuid); ``` ### Description @@ -636,15 +686,19 @@ Raw data is passed from native code to the callback as an [ArrayBuffer](#typed-a ### Quick Example Retrieves an [ArrayBuffer](#typed-arrays) when reading data. + ```javascript // read data from a characteristic, do something with output data -ble.read(device_id, service_uuid, characteristic_uuid, - function(data){ - console.log("Hooray we have data"+JSON.stringify(data)); - alert("Successfully read data from device."+JSON.stringify(data)); +ble.read( + device_id, + service_uuid, + characteristic_uuid, + function (data) { + console.log('Hooray we have data' + JSON.stringify(data)); + alert('Successfully read data from device.' + JSON.stringify(data)); }, - function(failure){ - alert("Failed to read characteristic from device."); + function (failure) { + alert('Failed to read characteristic from device.'); } ); ``` @@ -652,10 +706,11 @@ ble.read(device_id, service_uuid, characteristic_uuid, ## write Writes data to a characteristic. + ```javascript ble.write(device_id, service_uuid, characteristic_uuid, data, success, failure); // Or using await with promises -await ble.withPromises.write(device_id, service_uuid, characteristic_uuid, data) +await ble.withPromises.write(device_id, service_uuid, characteristic_uuid, data); ``` ### Description @@ -674,18 +729,19 @@ Function `write` writes data to a characteristic. ### Quick Example Use an [ArrayBuffer](#typed-arrays) when writing data. + ```javascript // send 1 byte to switch a light on var data = new Uint8Array(1); data[0] = 1; -ble.write(device_id, "FF10", "FF11", data.buffer, success, failure); +ble.write(device_id, 'FF10', 'FF11', data.buffer, success, failure); // send a 3 byte value with RGB color var data = new Uint8Array(3); -data[0] = 0xFF; // red +data[0] = 0xff; // red data[1] = 0x00; // green -data[2] = 0xFF; // blue -ble.write(device_id, "ccc0", "ccc1", data.buffer, success, failure); +data[2] = 0xff; // blue +ble.write(device_id, 'ccc0', 'ccc1', data.buffer, success, failure); // send a 32 bit integer var data = new Uint32Array(1); @@ -696,10 +752,11 @@ ble.write(device_id, SERVICE, CHARACTERISTIC, data.buffer, success, failure); ## writeWithoutResponse Writes data to a characteristic without confirmation from the peripheral. + ```javascript ble.writeWithoutResponse(device_id, service_uuid, characteristic_uuid, data, success, failure); // Or using await with promises -await ble.withPromises.writeWithoutResponse(device_id, service_uuid, characteristic_uuid, data) +await ble.withPromises.writeWithoutResponse(device_id, service_uuid, characteristic_uuid, data); ``` ### Description @@ -718,11 +775,12 @@ Function `writeWithoutResponse` writes data to a characteristic without a respon ## startNotification Register to be notified when the value of a characteristic changes. + ```javascript ble.startNotification(device_id, service_uuid, characteristic_uuid, success, failure); // Or using await with promises // Note, initial promise resolves or rejects depending on whether the subscribe was successful -await ble.withPromises.startNotification(device_id, success, failure) +await ble.withPromises.startNotification(device_id, success, failure); ``` ### Description @@ -742,23 +800,25 @@ See [Background Notifications on iOS](#background-notifications-on-ios) - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example + ```javascript -var onData = function(buffer) { +var onData = function (buffer) { // Decode the ArrayBuffer into a typed Array based on the data you expect var data = new Uint8Array(buffer); - alert("Button state changed to " + data[0]); -} + alert('Button state changed to ' + data[0]); +}; -ble.startNotification(device_id, "FFE0", "FFE1", onData, failure); +ble.startNotification(device_id, 'FFE0', 'FFE1', onData, failure); ``` ## stopNotification Stop being notified when the value of a characteristic changes. + ```javascript ble.stopNotification(device_id, service_uuid, characteristic_uuid, success, failure); // Or using await with promises -await ble.withPromises.stopNotification(device_id) +await ble.withPromises.stopNotification(device_id); ``` ### Description @@ -776,10 +836,11 @@ Function `stopNotification` stops a previously registered notification callback. ## isConnected Reports the connection status. + ```javascript ble.isConnected(device_id, success, failure); // Or using await with promises -await ble.withPromises.isConnected(device_id) +await ble.withPromises.isConnected(device_id); ``` ### Description @@ -795,14 +856,15 @@ NOTE that for many apps isConnected is unncessary. The app can track the connect - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example + ```javascript ble.isConnected( 'FFCA0B09-CB1D-4DC0-A1EF-31AFD3EDFB53', - function() { - console.log("Peripheral is connected"); + function () { + console.log('Peripheral is connected'); }, - function() { - console.log("Peripheral is *not* connected"); + function () { + console.log('Peripheral is *not* connected'); } ); ``` @@ -810,6 +872,7 @@ ble.isConnected( ## isEnabled Reports if bluetooth is enabled. + ```javascript ble.isEnabled(success, failure); ``` @@ -824,13 +887,14 @@ Function `isEnabled` calls the success callback when Bluetooth is enabled and th - **failure**: Error callback function, invoked when Bluetooth is disabled. ### Quick Example + ```javascript ble.isEnabled( - function() { - console.log("Bluetooth is enabled"); + function () { + console.log('Bluetooth is enabled'); }, - function() { - console.log("Bluetooth is *not* enabled"); + function () { + console.log('Bluetooth is *not* enabled'); } ); ``` @@ -838,6 +902,7 @@ ble.isEnabled( ## isLocationEnabled Reports if location services are enabled. + ```javascript ble.isLocationEnabled(success, failure); ``` @@ -856,13 +921,14 @@ Function `isLocationEnabled` calls the success callback when location services a - **failure**: Error callback function, invoked when location services are disabled. ### Quick Example + ```javascript ble.isLocationEnabled( - function() { - console.log("location services are enabled"); + function () { + console.log('location services are enabled'); }, - function() { - console.log("location services are *not* enabled"); + function () { + console.log('location services are *not* enabled'); } ); ``` @@ -870,11 +936,12 @@ ble.isLocationEnabled( ## startLocationStateNotifications Registers to be notified when Location service state changes on the device. + ```javascript ble.startLocationStateNotifications(success, failure); // Or using await with promises // Note, initial promise resolves or rejects depending on whether the subscribe was successful -await ble.withPromises.startLocationStateNotifications(success, failure) +await ble.withPromises.startLocationStateNotifications(success, failure); ``` ### Description @@ -891,21 +958,21 @@ Function `startLocationStateNotifications` calls the success callback when the L - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example + ```javascript -ble.startLocationStateNotifications( - function(enabled) { - console.log("Location is " + enabled); - } -); +ble.startLocationStateNotifications(function (enabled) { + console.log('Location is ' + enabled); +}); ``` ## stopLocationStateNotifications Stops state notifications. + ```javascript ble.stopLocationStateNotifications(success, failure); // Or using await with promises -await ble.withPromises.stopLocationStateNotifications() +await ble.withPromises.stopLocationStateNotifications(); ``` ### Description @@ -919,11 +986,12 @@ Function `stopLocationStateNotifications` calls the success callback when Locati ## startStateNotifications Registers to be notified when Bluetooth state changes on the device. + ```javascript ble.startStateNotifications(success, failure); // Or using await with promises // Note, initial promise resolves or rejects depending on whether the subscribe was successful -await ble.withPromises.startStateNotifications(success, failure) +await ble.withPromises.startStateNotifications(success, failure); ``` ### Description @@ -952,21 +1020,21 @@ Function `startStateNotifications` calls the success callback when the Bluetooth - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example + ```javascript -ble.startStateNotifications( - function(state) { - console.log("Bluetooth is " + state); - } -); +ble.startStateNotifications(function (state) { + console.log('Bluetooth is ' + state); +}); ``` ## stopStateNotifications Stops state notifications. + ```javascript ble.stopStateNotifications(success, failure); // Or using await with promises -await ble.withPromises.stopStateNotifications() +await ble.withPromises.stopStateNotifications(); ``` ### Description @@ -981,10 +1049,11 @@ Function `stopStateNotifications` calls the success callback when Bluetooth stat ## showBluetoothSettings Show the Bluetooth settings on the device. + ```javascript ble.showBluetoothSettings(success, failure); // Or using await with promises -await ble.withPromises.showBluetoothSettings() +await ble.withPromises.showBluetoothSettings(); ``` ### Description @@ -1003,6 +1072,7 @@ Function `showBluetoothSettings` opens the Bluetooth settings for the operating - **failure**: Error callback function, invoked when error occurs. [optional] ### Quick Example + ```javascript ble.showBluetoothSettings(); ``` @@ -1010,6 +1080,7 @@ ble.showBluetoothSettings(); ## enable Enable Bluetooth on the device. + ```javascript ble.enable(success, failure); // Or using await with promises @@ -1034,13 +1105,14 @@ If `enable` is called when Bluetooth is already enabled, the user will not promp - **failure**: Error callback function, invoked if the user does not enabled Bluetooth. ### Quick Example + ```javascript ble.enable( - function() { - console.log("Bluetooth is enabled"); + function () { + console.log('Bluetooth is enabled'); }, - function() { - console.log("The user did *not* enable Bluetooth"); + function () { + console.log('The user did *not* enable Bluetooth'); } ); ``` @@ -1048,6 +1120,7 @@ ble.enable( ## readRSSI Read the RSSI value on the device connection. + ```javascript ble.readRSSI(device_id, success, failure); ``` @@ -1063,25 +1136,35 @@ Samples the RSSI value (a measure of signal strength) on the connection to a blu - **failure**: Error callback function, invoked if there is no current connection or if there is an error reading the RSSI. ### Quick Example + ```javascript var rssiSample; -ble.connect(device_id, - function(device) { - rssiSample = setInterval(function() { - ble.readRSSI(device_id, function(rssi) { - console.log('read RSSI',rssi,'with device', device_id); - }, function(err) { - console.error('unable to read RSSI',err); - clearInterval(rssiSample); - }) - }, 5000); +ble.connect( + device_id, + function (device) { + rssiSample = setInterval(function () { + ble.readRSSI( + device_id, + function (rssi) { + console.log('read RSSI', rssi, 'with device', device_id); + }, + function (err) { + console.error('unable to read RSSI', err); + clearInterval(rssiSample); + } + ); + }, 5000); }, - function(err) { console.error('error connecting to device')} + function (err) { + console.error('error connecting to device'); + } ); ``` + ## connectedPeripheralsWithServices Find the connected peripherals offering the listed service UUIDs. + ```javascript ble.connectedPeripheralsWithServices([service], success, failure); ``` @@ -1103,6 +1186,7 @@ Retreives a list of the peripherals (containing any of the specified services) c ## peripheralsWithIdentifiers Find the connected peripherals offering the listed peripheral UUIDs. + ```javascript ble.peripheralsWithIdentifiers([uuids], success, failure); ``` @@ -1124,6 +1208,7 @@ Sends a list of known peripherals by their identifiers to the success callback. ## restoredBluetoothState Retrieve the CBManager restoration state (if applicable) + ```javascript ble.restoredBluetoothState(success, failure); // Or using await with promises @@ -1150,6 +1235,7 @@ If the application has no state restored, this will return an empty object. ## list Lists all peripherals discovered by the plugin due to scanning or connecting since app launch. + ```javascript ble.list(success, failure); // Or using await with promises @@ -1172,6 +1258,7 @@ Sends a list of bonded low energy peripherals to the success callback. ## bondedDevices Find the bonded devices. + ```javascript ble.bondedDevices(success, failure); // Or using await with promises @@ -1194,6 +1281,7 @@ Sends a list of bonded low energy peripherals to the success callback. ## l2cap.open Open an L2CAP channel with a connected peripheral. The PSM is assigned by the peripheral, or possibly defined by the Bluetooth standard. + ```javascript ble.l2cap.open(device_id, psm, connectCallback, disconnectCallback); // Or using await with promises @@ -1201,6 +1289,7 @@ await ble.withPromises.l2cap.open(device_id, psm, disconnectCallback); ``` Android supports additional arguments in the psm flag to select whether the L2CAP channel is insecure or secure (iOS does this automatically): + ```javascript ble.l2cap.open(device_id, { psm: psm, secureChannel: true }, connectCallback, disconnectCallback); // Or using await with promises @@ -1228,6 +1317,7 @@ The PSM (protocol/service multiplexer) is specified by the peripheral when it op ## l2cap.close Close an L2CAP channel. + ```javascript ble.l2cap.close(device_id, psm, success, failure); // Or using await with promises @@ -1253,6 +1343,7 @@ Closes an open L2CAP channel with the selected device. All pending reads and wri ## l2cap.receiveData Receive data from an L2CAP channel. + ```javascript ble.l2cap.receiveData(device_id, psm, dataCallback); ``` @@ -1275,6 +1366,7 @@ Sets the function to be called whenever bytes are received on the L2CAP channel. ## l2cap.write Write data to an L2CAP channel. + ```javascript ble.l2cap.write(device_id, psm, data, success, failure); // Or using await with promises @@ -1301,55 +1393,45 @@ Writes all data to an open L2CAP channel. If the data exceeds the available spac # Peripheral Data Peripheral Data is passed to the success callback when scanning and connecting. Limited data is passed when scanning. + ```json { "name": "Battery Demo", "id": "20:FF:D0:FF:D1:C0", - "advertising": [2,1,6,3,3,15,24,8,9,66,97,116,116,101,114,121], + "advertising": [2, 1, 6, 3, 3, 15, 24, 8, 9, 66, 97, 116, 116, 101, 114, 121], "rssi": -55 } ``` After connecting, the peripheral object also includes service, characteristic and descriptor information. + ```json { "name": "Battery Demo", "id": "20:FF:D0:FF:D1:C0", - "advertising": [2,1,6,3,3,15,24,8,9,66,97,116,116,101,114,121], + "advertising": [2, 1, 6, 3, 3, 15, 24, 8, 9, 66, 97, 116, 116, 101, 114, 121], "rssi": -55, - "services": [ - "1800", - "1801", - "180f" - ], + "services": ["1800", "1801", "180f"], "characteristics": [ { "service": "1800", "characteristic": "2a00", - "properties": [ - "Read" - ] + "properties": ["Read"] }, { "service": "1800", "characteristic": "2a01", - "properties": [ - "Read" - ] + "properties": ["Read"] }, { "service": "1801", "characteristic": "2a05", - "properties": [ - "Read" - ] + "properties": ["Read"] }, { "service": "180f", "characteristic": "2a19", - "properties": [ - "Read" - ], + "properties": ["Read"], "descriptors": [ { "uuid": "2901" @@ -1373,6 +1455,7 @@ To get consistent advertising data payloads across platforms, you can use the [ble-central-advertisements](https://github.com/jospete/ble-central-advertisements) module. ## Android + ```json { "name": "demo", @@ -1388,6 +1471,7 @@ Convert the advertising info to a Uint8Array for processing. `var adData = new U ## iOS Note that iOS uses the string value of the constants for the [Advertisement Data Retrieval Keys](https://developer.apple.com/library/ios/documentation/CoreBluetooth/Reference/CBCentralManagerDelegate_Protocol/index.html#//apple_ref/doc/constant_group/Advertisement_Data_Retrieval_Keys). This will likely change in the future. + ```json { "name": "demo" @@ -1408,6 +1492,7 @@ Note that iOS uses the string value of the constants for the [Advertisement Data ``` Some of the values such as `kCBAdvDataManufacturerData` are `ArrayBuffers`. The data won't print out, but you can convert it to bytes using `new Uint8Array(peripheral.advertisting.kCBAdvDataManufacturerData)`. Your application is responsible for parsing and decoding any binary data such as `kCBAdvDataManufacturerData` or `kCBAdvDataServiceData`. + ```javascript function onDiscoverDevice(device) { // log the device as JSON @@ -1418,7 +1503,6 @@ function onDiscoverDevice(device) { const mfgData = new Uint8Array(device.advertising.kCBAdvDataManufacturerData); console.log('Manufacturer Data is', mfgData); } - } ble.scan([], 5, onDiscoverDevice, onError); @@ -1438,12 +1522,13 @@ Scan must be initiated from a user action (click, touch, etc). This plugin uses typed Arrays or ArrayBuffers for sending and receiving data. This means that you need convert your data to ArrayBuffers before sending and from ArrayBuffers when receiving. + ```javascript // ASCII only function stringToBytes(string) { - var array = new Uint8Array(string.length); - for (var i = 0, l = string.length; i < l; i++) { - array[i] = string.charCodeAt(i); + var array = new Uint8Array(string.length); + for (var i = 0, l = string.length; i < l; i++) { + array[i] = string.charCodeAt(i); } return array.buffer; } @@ -1469,6 +1554,7 @@ Android applications will continue to receive notification while the application iOS applications need additional configuration to allow Bluetooth to run in the background. Add a new section to config.xml + ```xml diff --git a/types.d.ts b/types.d.ts index 5a2f7d09..53d12235 100644 --- a/types.d.ts +++ b/types.d.ts @@ -184,6 +184,10 @@ declare namespace BLECentralPlugin { readRSSI(device_id: string): Promise; + /* May be used to request (on Android) a larger MTU size to be able to send more data at once + [iOS] requestMtu is not supported on iOS. */ + requestMtu(device_id: string, mtu: number): Promise; + /* When Connecting to a peripheral android can request for the connection priority for faster communication. [iOS] requestConnectionPriority is not supported on iOS. */ requestConnectionPriority(device_id: string, priority: 'high' | 'balanced' | 'low'): Promise; diff --git a/www/ble.js b/www/ble.js index 4a0d742a..0ea06ed0 100644 --- a/www/ble.js +++ b/www/ble.js @@ -434,6 +434,12 @@ module.exports.withPromises = { }); }, + requestMtu: function (device_id, mtu) { + return new Promise(function (resolve, reject) { + module.exports.requestMtu(device_id, mtu, resolve, reject); + }); + }, + requestConnectionPriority: function (device_id, priority) { return new Promise(function (resolve, reject) { module.exports.requestConnectionPriority(device_id, priority, resolve, reject); From 1dcafeba3427746c20818f87d0bd86da91b25e4b Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Fri, 17 May 2024 09:26:13 +1000 Subject: [PATCH 62/86] 1.7.3 --- package.json | 2 +- plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6ae5d8d0..c4abac86 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.7.2", + "version": "1.7.3", "description": "Bluetooth Low Energy (BLE) Central Plugin", "scripts": { "slimify": "node tools/make-slim-variant.mjs", diff --git a/plugin.xml b/plugin.xml index 385f99cf..d5163cf4 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin From 8be79ef31e0ca342db348f14eda1d59e4a314573 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 26 Jun 2024 09:16:29 +1000 Subject: [PATCH 63/86] Support Cordova 13 in CI (#1019) --- .github/workflows/cordova.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cordova.yml b/.github/workflows/cordova.yml index 5482da2f..d5fe86b0 100644 --- a/.github/workflows/cordova.yml +++ b/.github/workflows/cordova.yml @@ -17,11 +17,17 @@ jobs: fail-fast: false matrix: include: - - jdk: 16 + - jdk: 17 cordova: latest platform: latest node: 18.x ubuntu: 22.04 + android-cmdline-tools-version: 10406996 + - jdk: 16 + cordova: 12 + platform: 12 + node: 18.x + ubuntu: 22.04 android-cmdline-tools-version: 9862592 - jdk: 11 cordova: 11 @@ -54,6 +60,7 @@ jobs: - name: Build test app run: | sdkmanager "build-tools;30.0.3" + sdkmanager "build-tools;34.0.0" npm install -g cordova@${{ matrix.cordova }} cordova create temp cd temp From 028f2df8f36416bcd364a97d08b0d4c7b2d88cc4 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 26 Jun 2024 08:45:15 +1000 Subject: [PATCH 64/86] More cleanly support isConnected usage with a bool return value Related to discussion on #1015 --- README.md | 26 ++++++++++++++++++++++++-- types.d.ts | 7 ++++++- www/ble.js | 12 ++++++++++-- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b2496d69..a82adebd 100644 --- a/README.md +++ b/README.md @@ -838,9 +838,14 @@ Function `stopNotification` stops a previously registered notification callback. Reports the connection status. ```javascript +// Callbacks ble.isConnected(device_id, success, failure); -// Or using await with promises -await ble.withPromises.isConnected(device_id); + +// Promises with boolean return +const isConnected = await ble.withPromises.isConnected(device_id, false); + +// Promises with rejection +await ble.withPromises.isConnected(device_id); // throws if not connected ``` ### Description @@ -858,6 +863,7 @@ NOTE that for many apps isConnected is unncessary. The app can track the connect ### Quick Example ```javascript +// Callbacks ble.isConnected( 'FFCA0B09-CB1D-4DC0-A1EF-31AFD3EDFB53', function () { @@ -867,6 +873,22 @@ ble.isConnected( console.log('Peripheral is *not* connected'); } ); + +// Promises with boolean return +const isConnected = await ble.withPromises.isConnected(device_id, false); +if (isConnected) { + console.log('Peripheral is connected'); +} else { + console.log('Peripheral is *not* connected'); +} + +// Promises with rejection +try { + await ble.withPromises.isConnected(device_id); + console.log('Peripheral is connected'); +} catch (e) { + console.log('Peripheral is *not* connected'); +} ``` ## isEnabled diff --git a/types.d.ts b/types.d.ts index 53d12235..78d31a68 100644 --- a/types.d.ts +++ b/types.d.ts @@ -160,9 +160,14 @@ declare namespace BLECentralPlugin { ): Promise; stopNotification(device_id: string, service_uuid: string, characteristic_uuid: string): Promise; - /* Returns a rejected promise if the device is not connected */ + /* Returns a resolved promise if the device is connected, + otherwise returns rejected promise if the device is not connected */ isConnected(device_id: string): Promise; + /* Returns a promise that resolves to true if the device is connected, + otherwise resolves to false if the device is not connected or an error occurs */ + isConnected(device_id: string, rejectWhenDisconnected: false): Promise; + /* Returns a rejected promise if bluetooth is not connected */ isEnabled(): Promise; diff --git a/www/ble.js b/www/ble.js index 0ea06ed0..066731eb 100644 --- a/www/ble.js +++ b/www/ble.js @@ -362,9 +362,17 @@ module.exports.withPromises = { }); }, - isConnected: function (device_id) { + isConnected: function (device_id, rejectWhenDisconnected) { return new Promise(function (resolve, reject) { - module.exports.isConnected(device_id, resolve, reject); + if (rejectWhenDisconnected === false) { + module.exports.isConnected( + device_id, + () => resolve(true), + () => resolve(false) + ); + } else { + module.exports.isConnected(device_id, resolve, reject); + } }); }, From 4bb392c241b160e8240d15918d8bcd652c3dd410 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 26 Jun 2024 10:30:10 +1000 Subject: [PATCH 65/86] Prepare for 1.7.4 release --- CHANGELOG.md | 4 ++++ README.md | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2517d684..c88c4a29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.7.4 + +- More cleanly support isConnected usage with a bool return value #1018 with review from @MaximBelov + ## 1.7.3 - Android: Expose Promise version of `requestMtu` for async/await diff --git a/README.md b/README.md index a82adebd..662fd083 100644 --- a/README.md +++ b/README.md @@ -1634,9 +1634,7 @@ Run the app on your phone ## Release 1. `npm version patch` -2. Align `plugin.xml` version with npm version -3. Update release notes based on `git log --oneline --no-merges ...master` -4. `npm publish` +2. `npm publish` ## Release (slim) From 2ebde634d8e452e469eafc9626ea2fe00d47a856 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Wed, 26 Jun 2024 10:32:35 +1000 Subject: [PATCH 66/86] 1.7.4 --- package.json | 2 +- plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c4abac86..65ba2752 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.7.3", + "version": "1.7.4", "description": "Bluetooth Low Energy (BLE) Central Plugin", "scripts": { "slimify": "node tools/make-slim-variant.mjs", diff --git a/plugin.xml b/plugin.xml index d5163cf4..a8a9888f 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin From 2ce4c569c61183a5ea0c8de5c95ee7be9581e970 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 13 Jul 2024 10:56:52 +1000 Subject: [PATCH 67/86] Improve input validation of UUIDs on iOS to avoid crashes (#1014, #905) --- src/ios/BLECentralPlugin.m | 189 ++++++++++++++++++++++++++++--------- 1 file changed, 145 insertions(+), 44 deletions(-) diff --git a/src/ios/BLECentralPlugin.m b/src/ios/BLECentralPlugin.m index f8b12148..a4242790 100644 --- a/src/ios/BLECentralPlugin.m +++ b/src/ios/BLECentralPlugin.m @@ -22,8 +22,8 @@ @interface BLECentralPlugin() { NSDictionary *bluetoothStates; } -- (CBPeripheral *)findPeripheralByUUID:(NSString *)uuid; -- (CBPeripheral *)retrievePeripheralWithUUID:(NSString *)uuid; +- (CBPeripheral *)findPeripheralByUUID:(NSUUID *)uuid; +- (CBPeripheral *)retrievePeripheralWithUUID:(NSUUID *)uuid; - (void)stopScanTimer:(NSTimer *)timer; @end @@ -124,7 +124,11 @@ - (void)connect:(CDVInvokedUrlCommand *)command { return; } - NSString *uuid = [command argumentAtIndex:0]; + NSUUID *uuid = [self getUUID:command argumentAtIndex:0]; + if (uuid == nil) { + return; + } + CBPeripheral *peripheral = [self findPeripheralByUUID:uuid]; if (!peripheral) { peripheral = [self retrievePeripheralWithUUID:uuid]; @@ -158,7 +162,10 @@ - (void)autoConnect:(CDVInvokedUrlCommand *)command { return; } - NSString *uuid = [command argumentAtIndex:0]; + NSUUID *uuid = [self getUUID:command argumentAtIndex:0]; + if (uuid == nil) { + return; + } CBPeripheral *peripheral = [self findPeripheralByUUID:uuid]; if (!peripheral) { @@ -184,7 +191,11 @@ - (void)autoConnect:(CDVInvokedUrlCommand *)command { - (void)disconnect:(CDVInvokedUrlCommand*)command { NSLog(@"disconnect"); - NSString *uuid = [command argumentAtIndex:0]; + NSUUID *uuid = [self getUUID:command argumentAtIndex:0]; + if (uuid == nil) { + return; + } + CBPeripheral *peripheral = [self findPeripheralByUUID:uuid]; if (!peripheral) { @@ -367,7 +378,21 @@ - (void)startScanWithOptions:(CDVInvokedUrlCommand*)command { discoverPeripheralCallbackId = [command.callbackId copy]; NSArray *serviceUUIDStrings = [command argumentAtIndex:0]; + if (serviceUUIDStrings != nil && ![serviceUUIDStrings isKindOfClass:[NSArray class]]) { + NSLog(@"Malformed UUID"); + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Malformed UUID"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + NSArray *serviceUUIDs = [self uuidStringsToCBUUIDs:serviceUUIDStrings]; + if (serviceUUIDs == nil) { + NSLog(@"Malformed UUID"); + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Malformed UUID"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + NSDictionary *options = command.arguments[1]; NSMutableDictionary *scanOptions = [NSMutableDictionary new]; @@ -453,8 +478,11 @@ - (void)onReset { - (void)readRSSI:(CDVInvokedUrlCommand*)command { NSLog(@"readRSSI"); - NSString *uuid = [command argumentAtIndex:0]; - + NSUUID *uuid = [self getUUID:command argumentAtIndex:0]; + if (uuid == nil) { + return; + } + CBPeripheral *peripheral = [self findPeripheralByUUID:uuid]; if (peripheral && peripheral.state == CBPeripheralStateConnected) { @@ -482,8 +510,21 @@ - (void)connectedPeripheralsWithServices:(CDVInvokedUrlCommand*)command { return; } - NSArray *serviceUUIDStrings = [command argumentAtIndex:0]; + NSArray *serviceUUIDStrings = [command argumentAtIndex:0]; + if (serviceUUIDStrings != nil && ![serviceUUIDStrings isKindOfClass:[NSArray class]]) { + NSLog(@"Malformed UUID"); + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Malformed UUID"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } + NSArray *serviceUUIDs = [self uuidStringsToCBUUIDs:serviceUUIDStrings]; + if (serviceUUIDs == nil) { + NSLog(@"Malformed UUID"); + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Malformed UUID"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } NSArray *connectedPeripherals = [manager retrieveConnectedPeripheralsWithServices:serviceUUIDs]; NSMutableArray *connected = [NSMutableArray new]; @@ -505,6 +546,12 @@ - (void)peripheralsWithIdentifiers:(CDVInvokedUrlCommand*)command { NSLog(@"peripheralsWithIdentifiers"); NSArray *identifierUUIDStrings = [command argumentAtIndex:0]; NSArray *identifiers = [self uuidStringsToNSUUIDs:identifierUUIDStrings]; + if (identifiers == nil) { + NSLog(@"Malformed UUID"); + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Malformed UUID"]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return; + } NSArray *foundPeripherals = [manager retrievePeripheralsWithIdentifiers:identifiers]; // TODO are any of these connected? @@ -524,7 +571,11 @@ - (void)peripheralsWithIdentifiers:(CDVInvokedUrlCommand*)command { - (void)closeL2Cap:(CDVInvokedUrlCommand*)command { NSLog(@"closeL2Cap"); - NSString *uuid = [command argumentAtIndex:0]; + NSUUID *uuid = [self getUUID:command argumentAtIndex:0]; + if (uuid == nil) { + return; + } + NSNumber *psm = [command argumentAtIndex:1]; CBPeripheral *peripheral = [self findPeripheralByUUID:uuid]; @@ -545,7 +596,11 @@ - (void)closeL2Cap:(CDVInvokedUrlCommand*)command { - (void)openL2Cap:(CDVInvokedUrlCommand*)command { NSLog(@"openL2Cap"); - NSString *uuid = [command argumentAtIndex:0]; + NSUUID *uuid = [self getUUID:command argumentAtIndex:0]; + if (uuid == nil) { + return; + } + NSNumber *psm = [command argumentAtIndex:1]; CBPeripheral *peripheral = [self findPeripheralByUUID:uuid]; @@ -565,7 +620,11 @@ - (void)openL2Cap:(CDVInvokedUrlCommand*)command { - (void)receiveDataL2Cap:(CDVInvokedUrlCommand*)command { NSLog(@"receiveDataL2Cap"); - NSString *uuid = [command argumentAtIndex:0]; + NSUUID *uuid = [self getUUID:command argumentAtIndex:0]; + if (uuid == nil) { + return; + } + NSNumber *psm = [command argumentAtIndex:1]; CBPeripheral *peripheral = [self findPeripheralByUUID:uuid]; @@ -583,7 +642,11 @@ - (void)receiveDataL2Cap:(CDVInvokedUrlCommand*)command { - (void)writeL2Cap:(CDVInvokedUrlCommand *)command { NSLog(@"writeL2Cap"); - NSString *uuid = [command argumentAtIndex:0]; + NSUUID *uuid = [self getUUID:command argumentAtIndex:0]; + if (uuid == nil) { + return; + } + NSNumber *psm = [command argumentAtIndex:1]; NSData *message = [command argumentAtIndex:2]; // This is binary CBPeripheral *peripheral = [self findPeripheralByUUID:uuid]; @@ -921,14 +984,14 @@ - (void)peripheral:(CBPeripheral *)peripheral didOpenL2CAPChannel:(CBL2CAPChanne #pragma mark - internal implemetation -- (CBPeripheral*)findPeripheralByUUID:(NSString*)uuid { +- (CBPeripheral*)findPeripheralByUUID:(NSUUID*)uuid { CBPeripheral *peripheral = nil; for (CBPeripheral *p in peripherals) { - NSString* other = p.identifier.UUIDString; + NSUUID* other = p.identifier; - if ([uuid isEqualToString:other]) { + if ([uuid isEqual:other]) { peripheral = p; break; } @@ -936,8 +999,7 @@ - (CBPeripheral*)findPeripheralByUUID:(NSString*)uuid { return peripheral; } -- (CBPeripheral*)retrievePeripheralWithUUID:(NSString*)uuid { - NSUUID *typedUUID = [[NSUUID alloc] initWithUUIDString:uuid]; +- (CBPeripheral*)retrievePeripheralWithUUID:(NSUUID*)typedUUID { NSArray *existingPeripherals = [manager retrievePeripheralsWithIdentifiers:@[typedUUID]]; CBPeripheral *peripheral = nil; if ([existingPeripherals count] > 0) { @@ -1008,26 +1070,52 @@ -(int) compareCBUUID:(CBUUID *) UUID1 UUID2:(CBUUID *)UUID2 { return 0; } +-(NSUUID*) getUUID:(CDVInvokedUrlCommand*)command argumentAtIndex:(NSUInteger)index { + NSLog(@"getUUID"); + + NSString *uuidString = [command argumentAtIndex:index withDefault:@"" andClass:[NSString class]]; + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + if (uuid == nil) { + NSString *errorMessage = [NSString stringWithFormat:@"Malformed UUID: %@", [command argumentAtIndex:index]]; + NSLog(@"%@", errorMessage); + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return nil; + } + return uuid; +} + // expecting deviceUUID, serviceUUID, characteristicUUID in command.arguments -(BLECommandContext*) getData:(CDVInvokedUrlCommand*)command prop:(CBCharacteristicProperties)prop { NSLog(@"getData"); CDVPluginResult *pluginResult = nil; - NSString *deviceUUIDString = [command argumentAtIndex:0]; - NSString *serviceUUIDString = [command argumentAtIndex:1]; - NSString *characteristicUUIDString = [command argumentAtIndex:2]; + NSUUID *deviceUUID = [self getUUID:command argumentAtIndex:0]; + if (deviceUUID == nil) { + return nil; + } - CBUUID *serviceUUID = [CBUUID UUIDWithString:serviceUUIDString]; - CBUUID *characteristicUUID = [CBUUID UUIDWithString:characteristicUUIDString]; + NSUUID *serviceNSUUID = [self getUUID:command argumentAtIndex:1]; + if (serviceNSUUID == nil) { + return nil; + } + + NSUUID *characteristicNSUUID = [self getUUID:command argumentAtIndex:2]; + if (characteristicNSUUID == nil) { + return nil; + } + + CBUUID *serviceUUID = [CBUUID UUIDWithNSUUID:serviceNSUUID]; + CBUUID *characteristicUUID = [CBUUID UUIDWithNSUUID:characteristicNSUUID]; - CBPeripheral *peripheral = [self findPeripheralByUUID:deviceUUIDString]; + CBPeripheral *peripheral = [self findPeripheralByUUID:deviceUUID]; if (!peripheral) { - NSLog(@"Could not find peripheral with UUID %@", deviceUUIDString); + NSLog(@"Could not find peripheral with UUID %@", deviceUUID); - NSString *errorMessage = [NSString stringWithFormat:@"Could not find peripheral with UUID %@", deviceUUIDString]; + NSString *errorMessage = [NSString stringWithFormat:@"Could not find peripheral with UUID %@", deviceUUID]; pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; @@ -1036,16 +1124,11 @@ -(BLECommandContext*) getData:(CDVInvokedUrlCommand*)command prop:(CBCharacteris CBService *service = [self findServiceFromUUID:serviceUUID p:peripheral]; - if (!service) - { - NSLog(@"Could not find service with UUID %@ on peripheral with UUID %@", - serviceUUIDString, - peripheral.identifier.UUIDString); - - + if (!service) { NSString *errorMessage = [NSString stringWithFormat:@"Could not find service with UUID %@ on peripheral with UUID %@", - serviceUUIDString, + serviceNSUUID.UUIDString, peripheral.identifier.UUIDString]; + NSLog(@"%@", errorMessage); pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; @@ -1064,18 +1147,13 @@ -(BLECommandContext*) getData:(CDVInvokedUrlCommand*)command prop:(CBCharacteris characteristic = [self findCharacteristicFromUUID:characteristicUUID service:service]; } - if (!characteristic) - { - NSLog(@"Could not find characteristic with UUID %@ on service with UUID %@ on peripheral with UUID %@", - characteristicUUIDString, - serviceUUIDString, - peripheral.identifier.UUIDString); - + if (!characteristic) { NSString *errorMessage = [NSString stringWithFormat: @"Could not find characteristic with UUID %@ on service with UUID %@ on peripheral with UUID %@", - characteristicUUIDString, - serviceUUIDString, + characteristicNSUUID.UUIDString, + serviceNSUUID.UUIDString, peripheral.identifier.UUIDString]; + NSLog(@"%@", errorMessage); pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; @@ -1181,7 +1259,19 @@ - (NSString*) centralManagerStateToString: (int)state { - (NSArray *) uuidStringsToCBUUIDs: (NSArray *)uuidStrings { NSMutableArray *uuids = [NSMutableArray new]; for (int i = 0; i < [uuidStrings count]; i++) { - CBUUID *uuid = [CBUUID UUIDWithString:[uuidStrings objectAtIndex: i]]; + NSString *uuidString = [uuidStrings objectAtIndex: i]; + if (![uuidString isKindOfClass:[NSString class]]) { + NSLog(@"Malformed UUID found: %@", uuidString); + return nil; + } + + NSUUID *nsuuid = [[NSUUID alloc]initWithUUIDString:uuidString]; + if (nsuuid == nil) { + NSLog(@"Malformed UUID found: %@", uuidString); + return nil; + } + + CBUUID *uuid = [CBUUID UUIDWithNSUUID:nsuuid]; [uuids addObject:uuid]; } return uuids; @@ -1190,7 +1280,18 @@ - (NSString*) centralManagerStateToString: (int)state { - (NSArray *) uuidStringsToNSUUIDs: (NSArray *)uuidStrings { NSMutableArray *uuids = [NSMutableArray new]; for (int i = 0; i < [uuidStrings count]; i++) { - NSUUID *uuid = [[NSUUID alloc]initWithUUIDString:[uuidStrings objectAtIndex: i]]; + NSString *uuidString = [uuidStrings objectAtIndex: i]; + if (![uuidString isKindOfClass:[NSString class]]) { + NSLog(@"Malformed UUID found: %@", uuidString); + return nil; + } + + NSUUID *uuid = [[NSUUID alloc]initWithUUIDString:uuidString]; + if (uuid == nil) { + NSLog(@"Malformed UUID found: %@", uuidString); + return nil; + } + [uuids addObject:uuid]; } return uuids; From bfab8c1a10e1b67ef772911c03023639dc9474a4 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 13 Jul 2024 14:32:05 +1000 Subject: [PATCH 68/86] Address iOS deprecation warnings & general warnings (#919) --- src/ios/BLECentralPlugin.m | 50 +++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/ios/BLECentralPlugin.m b/src/ios/BLECentralPlugin.m index a4242790..a1bb08a1 100644 --- a/src/ios/BLECentralPlugin.m +++ b/src/ios/BLECentralPlugin.m @@ -63,12 +63,12 @@ - (void)pluginInitialize { stopNotificationCallbacks = [NSMutableDictionary new]; l2CapContexts = [NSMutableDictionary new]; bluetoothStates = [NSDictionary dictionaryWithObjectsAndKeys: - @"unknown", @(CBCentralManagerStateUnknown), - @"resetting", @(CBCentralManagerStateResetting), - @"unsupported", @(CBCentralManagerStateUnsupported), - @"unauthorized", @(CBCentralManagerStateUnauthorized), - @"off", @(CBCentralManagerStatePoweredOff), - @"on", @(CBCentralManagerStatePoweredOn), + @"unknown", @(CBManagerStateUnknown), + @"resetting", @(CBManagerStateResetting), + @"unsupported", @(CBManagerStateUnsupported), + @"unauthorized", @(CBManagerStateUnauthorized), + @"off", @(CBManagerStatePoweredOff), + @"on", @(CBManagerStatePoweredOn), nil]; readRSSICallbacks = [NSMutableDictionary new]; } @@ -353,14 +353,14 @@ - (void)stopNotification:(CDVInvokedUrlCommand*)command { - (void)isEnabled:(CDVInvokedUrlCommand*)command { CDVPluginResult *pluginResult = nil; - int bluetoothState = [manager state]; + CBManagerState bluetoothState = [manager state]; - BOOL enabled = bluetoothState == CBCentralManagerStatePoweredOn; + BOOL enabled = bluetoothState == CBManagerStatePoweredOn; if (enabled) { pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; } else { - pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsInt:bluetoothState]; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsNSInteger:bluetoothState]; } [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } @@ -446,8 +446,8 @@ - (void)startStateNotifications:(CDVInvokedUrlCommand *)command { if (stateCallbackId == nil) { stateCallbackId = [command.callbackId copy]; - int bluetoothState = [manager state]; - NSString *state = [bluetoothStates objectForKey:[NSNumber numberWithInt:bluetoothState]]; + CBManagerState bluetoothState = [manager state]; + NSString *state = [bluetoothStates objectForKey:@(bluetoothState)]; pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:state]; [pluginResult setKeepCallbackAsBool:TRUE]; NSLog(@"Start state notifications on callback %@", stateCallbackId); @@ -689,7 +689,7 @@ - (void)centralManagerDidUpdateState:(CBCentralManager *)central { NSLog(@"Status of CoreBluetooth central manager changed %ld %@", (long)central.state, [self centralManagerStateToString: central.state]); - if (central.state == CBCentralManagerStateUnsupported) + if (central.state == CBManagerStateUnsupported) { NSLog(@"============================================================="); NSLog(@"WARNING: This hardware does not support Bluetooth Low Energy."); @@ -1234,21 +1234,21 @@ -(void) cleanupOperationCallbacks: (CBPeripheral *)peripheral withResult:(CDVPlu #pragma mark - util -- (NSString*) centralManagerStateToString: (int)state { +- (NSString*) centralManagerStateToString: (CBManagerState)state { switch(state) { - case CBCentralManagerStateUnknown: - return @"State unknown (CBCentralManagerStateUnknown)"; - case CBCentralManagerStateResetting: - return @"State resetting (CBCentralManagerStateUnknown)"; - case CBCentralManagerStateUnsupported: - return @"State BLE unsupported (CBCentralManagerStateResetting)"; - case CBCentralManagerStateUnauthorized: - return @"State unauthorized (CBCentralManagerStateUnauthorized)"; - case CBCentralManagerStatePoweredOff: - return @"State BLE powered off (CBCentralManagerStatePoweredOff)"; - case CBCentralManagerStatePoweredOn: - return @"State powered up and ready (CBCentralManagerStatePoweredOn)"; + case CBManagerStateUnknown: + return @"State unknown (CBManagerStateUnknown)"; + case CBManagerStateResetting: + return @"State resetting (CBManagerStateUnknown)"; + case CBManagerStateUnsupported: + return @"State BLE unsupported (CBManagerStateResetting)"; + case CBManagerStateUnauthorized: + return @"State unauthorized (CBManagerStateUnauthorized)"; + case CBManagerStatePoweredOff: + return @"State BLE powered off (CBManagerStatePoweredOff)"; + case CBManagerStatePoweredOn: + return @"State powered up and ready (CBManagerStatePoweredOn)"; default: return @"State unknown"; } From 28a610382a4fa2200bd71ff14e224fd6cc236fca Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 13 Jul 2024 15:04:08 +1000 Subject: [PATCH 69/86] Harden up iOS service discovery (#741) --- src/ios/BLECentralPlugin.m | 47 +++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/ios/BLECentralPlugin.m b/src/ios/BLECentralPlugin.m index a1bb08a1..086de15c 100644 --- a/src/ios/BLECentralPlugin.m +++ b/src/ios/BLECentralPlugin.m @@ -780,6 +780,17 @@ - (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(C - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error { NSLog(@"didDiscoverServices"); + if (error) { + NSLog(@"%@", error); + NSString *connectCallbackId = [connectCallbacks valueForKey:[peripheral uuidAsString]]; + if (connectCallbackId) { + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; + [pluginResult setKeepCallbackAsBool:TRUE]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:connectCallbackId]; + } + return; + } + // save the services to tell when all characteristics have been discovered NSMutableSet *servicesForPeriperal = [NSMutableSet new]; [servicesForPeriperal addObjectsFromArray:peripheral.services]; @@ -794,25 +805,39 @@ - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForServi NSLog(@"didDiscoverCharacteristicsForService"); NSString *peripheralUUIDString = [peripheral uuidAsString]; - NSString *connectCallbackId = [connectCallbacks valueForKey:peripheralUUIDString]; - NSMutableSet *latch = [connectCallbackLatches valueForKey:peripheralUUIDString]; - - [latch removeObject:service]; - - if ([latch count] == 0) { - // Call success callback for connect + + if (error) { + NSLog(@"%@", error); + [connectCallbackLatches removeObjectForKey:peripheralUUIDString]; + NSString *connectCallbackId = [connectCallbacks valueForKey:peripheralUUIDString]; if (connectCallbackId) { - CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[peripheral asDictionary]]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[error localizedDescription]]; [pluginResult setKeepCallbackAsBool:TRUE]; [self.commandDelegate sendPluginResult:pluginResult callbackId:connectCallbackId]; } - [connectCallbackLatches removeObjectForKey:peripheralUUIDString]; - } - + return; + } + NSLog(@"Found characteristics for service %@", service); for (CBCharacteristic *characteristic in service.characteristics) { NSLog(@"Characteristic %@", characteristic); } + + NSMutableSet *latch = [connectCallbackLatches valueForKey:peripheralUUIDString]; + if (latch) { + [latch removeObject:service]; + + if ([latch count] == 0) { + [connectCallbackLatches removeObjectForKey:peripheralUUIDString]; + // Call success callback for connect + NSString *connectCallbackId = [connectCallbacks valueForKey:peripheralUUIDString]; + if (connectCallbackId) { + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[peripheral asDictionary]]; + [pluginResult setKeepCallbackAsBool:TRUE]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:connectCallbackId]; + } + } + } } - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error { From fab4fe43d57604d3b25aee0b94e52a54e6fb04b4 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 8 Jun 2024 09:34:28 +1000 Subject: [PATCH 70/86] Suppress permission-related lint warnings on Android --- README.md | 2 +- src/android/BLECentralPlugin.java | 156 +++++++++++++++++------------- src/android/BLECommand.java | 25 +---- src/android/L2CAPContext.java | 4 +- src/android/Peripheral.java | 50 ++++++---- 5 files changed, 129 insertions(+), 108 deletions(-) diff --git a/README.md b/README.md index 662fd083..c6f26da0 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ See the [examples](https://github.com/don/cordova-plugin-ble-central/tree/master ## Supported Platforms - iOS -- Android (likely supports 6+, but 8.1 or greater recommended) +- Android 6+ (8.1 or greater recommended) - Browser (where navigator.bluetooth is supported) # Installing diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index ec755343..bd039cb7 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -15,6 +15,7 @@ package com.megster.cordova.ble.central; import android.Manifest; +import android.annotation.SuppressLint; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -170,6 +171,7 @@ protected void pluginInitialize() { } } + @SuppressLint("MissingPermission") @Override public void onDestroy() { removeStateListener(); @@ -180,6 +182,7 @@ public void onDestroy() { } } + @SuppressLint("MissingPermission") @Override public void onReset() { removeStateListener(); @@ -196,7 +199,7 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback if (bluetoothAdapter == null) { Activity activity = cordova.getActivity(); - boolean hardwareSupportsBLE = activity.getApplicationContext() + @SuppressLint("ObsoleteSdkInt") boolean hardwareSupportsBLE = activity.getApplicationContext() .getPackageManager() .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) && Build.VERSION.SDK_INT >= 18; @@ -434,79 +437,86 @@ public boolean execute(String action, CordovaArgs args, CallbackContext callback validAction = false; break; } - - switch (options.optString("callbackType", "")) { - case "": - break; - case "all": - scanSettings.setCallbackType( ScanSettings.CALLBACK_TYPE_ALL_MATCHES ); - break; - case "first": - scanSettings.setCallbackType( ScanSettings.CALLBACK_TYPE_FIRST_MATCH ); - break; - case "lost": - scanSettings.setCallbackType( ScanSettings.CALLBACK_TYPE_MATCH_LOST ); - break; - default: - callbackContext.error("callbackType must be one of: all | first | lost"); - validAction = false; - break; + if (Build.VERSION.SDK_INT >= 23) { + switch (options.optString("callbackType", "")) { + case "": + break; + case "all": + scanSettings.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES); + break; + case "first": + scanSettings.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH); + break; + case "lost": + scanSettings.setCallbackType(ScanSettings.CALLBACK_TYPE_MATCH_LOST); + break; + default: + callbackContext.error("callbackType must be one of: all | first | lost"); + validAction = false; + break; + } } - switch (options.optString("matchMode", "")) { - case "": - break; - case "aggressive": - scanSettings.setMatchMode( ScanSettings.MATCH_MODE_AGGRESSIVE ); - break; - case "sticky": - scanSettings.setMatchMode( ScanSettings.MATCH_MODE_STICKY ); - break; - default: - callbackContext.error("matchMode must be one of: aggressive | sticky"); - validAction = false; - break; + if (Build.VERSION.SDK_INT >= 23) { + switch (options.optString("matchMode", "")) { + case "": + break; + case "aggressive": + scanSettings.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE); + break; + case "sticky": + scanSettings.setMatchMode(ScanSettings.MATCH_MODE_STICKY); + break; + default: + callbackContext.error("matchMode must be one of: aggressive | sticky"); + validAction = false; + break; + } } - switch (options.optString("numOfMatches", "")) { - case "": - break; - case "one": - scanSettings.setNumOfMatches( ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT ); - break; - case "few": - scanSettings.setNumOfMatches( ScanSettings.MATCH_NUM_FEW_ADVERTISEMENT ); - break; - case "max": - scanSettings.setNumOfMatches( ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT ); - break; - default: - callbackContext.error("numOfMatches must be one of: one | few | max"); - validAction = false; - break; + if (Build.VERSION.SDK_INT >= 23) { + switch (options.optString("numOfMatches", "")) { + case "": + break; + case "one": + scanSettings.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT); + break; + case "few": + scanSettings.setNumOfMatches(ScanSettings.MATCH_NUM_FEW_ADVERTISEMENT); + break; + case "max": + scanSettings.setNumOfMatches(ScanSettings.MATCH_NUM_MAX_ADVERTISEMENT); + break; + default: + callbackContext.error("numOfMatches must be one of: one | few | max"); + validAction = false; + break; + } } - switch (options.optString("phy", "")) { - case "": - break; - case "1m": - scanSettings.setPhy( BluetoothDevice.PHY_LE_1M ); - break; - case "coded": - scanSettings.setPhy( BluetoothDevice.PHY_LE_CODED ); - break; - case "all": - scanSettings.setPhy( ScanSettings.PHY_LE_ALL_SUPPORTED ); - break; - default: - callbackContext.error("phy must be one of: 1m | coded | all"); - validAction = false; - break; + if (Build.VERSION.SDK_INT >= 26 /*O*/) { + switch (options.optString("phy", "")) { + case "": + break; + case "1m": + scanSettings.setPhy(BluetoothDevice.PHY_LE_1M); + break; + case "coded": + scanSettings.setPhy(BluetoothDevice.PHY_LE_CODED); + break; + case "all": + scanSettings.setPhy(ScanSettings.PHY_LE_ALL_SUPPORTED); + break; + default: + callbackContext.error("phy must be one of: 1m | coded | all"); + validAction = false; + break; + } } if (validAction) { String LEGACY = "legacy"; - if (!options.isNull(LEGACY)) + if (Build.VERSION.SDK_INT >= 26 /*O*/ && !options.isNull(LEGACY)) scanSettings.setLegacy( options.getBoolean(LEGACY) ); long reportDelay = options.optLong("reportDelay", -1 ); @@ -573,6 +583,7 @@ private void enableBluetooth(CallbackContext callbackContext) { cordova.startActivityForResult(this, intent, REQUEST_ENABLE_BLUETOOTH); } + @SuppressLint("MissingPermission") private void getBondedDevices(CallbackContext callbackContext) { if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) { @@ -610,6 +621,7 @@ private UUID[] parseServiceUUIDList(JSONArray jsonArray) throws JSONException { return serviceUUIDs.toArray(new UUID[jsonArray.length()]); } + @SuppressLint("MissingPermission") private void onBluetoothStateChange(Intent intent) { final String action = intent.getAction(); @@ -718,6 +730,7 @@ private void removeLocationStateListener() { this.locationStateReceiver = null; } + @SuppressLint("MissingPermission") private void connect(CallbackContext callbackContext, String macAddress) { if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) { @@ -734,7 +747,7 @@ private void connect(CallbackContext callbackContext, String macAddress) { return; } - if (!peripherals.containsKey(macAddress) && BLECentralPlugin.this.bluetoothAdapter.checkBluetoothAddress(macAddress)) { + if (!peripherals.containsKey(macAddress) && BluetoothAdapter.checkBluetoothAddress(macAddress)) { BluetoothDevice device = BLECentralPlugin.this.bluetoothAdapter.getRemoteDevice(macAddress); Peripheral peripheral = new Peripheral(device); peripherals.put(macAddress, peripheral); @@ -751,6 +764,7 @@ private void connect(CallbackContext callbackContext, String macAddress) { } + @SuppressLint("MissingPermission") private void autoConnect(CallbackContext callbackContext, String macAddress) { if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S @@ -788,6 +802,7 @@ private void autoConnect(CallbackContext callbackContext, String macAddress) { } + @SuppressLint("MissingPermission") private void disconnect(CallbackContext callbackContext, String macAddress) { Peripheral peripheral = peripherals.get(macAddress); @@ -819,6 +834,7 @@ private void setPin(CallbackContext callbackContext, final String pin) { } broadCastReceiver = new BroadcastReceiver() { + @SuppressLint("MissingPermission") @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -846,6 +862,7 @@ public void onReceive(Context context, Intent intent) { } } + @SuppressLint("MissingPermission") private void bond(CallbackContext callbackContext, String macAddress, boolean usePairingDialog) { if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S List missingPermissions = new ArrayList(); @@ -879,6 +896,7 @@ private void bond(CallbackContext callbackContext, String macAddress, boolean us } } + @SuppressLint("MissingPermission") private void unbond(CallbackContext callbackContext, String macAddress) { if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) { @@ -903,6 +921,7 @@ private void unbond(CallbackContext callbackContext, String macAddress) { } } + @SuppressLint("MissingPermission") private void readBondState(CallbackContext callbackContext, String macAddress) { if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) { @@ -927,6 +946,7 @@ private void readBondState(CallbackContext callbackContext, String macAddress) { } } + @SuppressLint("MissingPermission") private void requestMtu(CallbackContext callbackContext, String macAddress, int mtuValue) { Peripheral peripheral = peripherals.get(macAddress); if (peripheral != null) { @@ -938,6 +958,7 @@ private void requestMtu(CallbackContext callbackContext, String macAddress, int } } + @SuppressLint("MissingPermission") private void requestConnectionPriority(CallbackContext callbackContext, String macAddress, String priority) { Peripheral peripheral = peripherals.get(macAddress); @@ -1031,6 +1052,7 @@ private void write(CallbackContext callbackContext, String macAddress, UUID serv } + @SuppressLint("MissingPermission") private void connectL2cap(CallbackContext callbackContext, String macAddress, int psm, boolean secureChannel) { Peripheral peripheral = peripherals.get(macAddress); if (peripheral == null) { @@ -1130,6 +1152,7 @@ private void removeNotifyCallback(CallbackContext callbackContext, String macAdd } private ScanCallback leScanCallback = new ScanCallback() { + @SuppressLint("MissingPermission") @Override public void onScanResult(int callbackType, ScanResult result) { LOG.w(TAG, "Scan Result"); @@ -1182,6 +1205,7 @@ public void onScanFailed(int errorCode) { }; + @SuppressLint("MissingPermission") private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] serviceUUIDs, int scanSeconds, ScanSettings scanSettings) { if (!locationServicesEnabled() && Build.VERSION.SDK_INT < 31) { @@ -1281,6 +1305,7 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic callbackContext.sendPluginResult(result); } + @SuppressLint("MissingPermission") private void stopScan() { stopScanHandler.removeCallbacks(stopScanRunnable); if (bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { @@ -1305,6 +1330,7 @@ private boolean locationServicesEnabled() { return (locationMode > 0); } + @SuppressLint("MissingPermission") private void listKnownDevices(CallbackContext callbackContext) { if (COMPILE_SDK_VERSION >= 31 && Build.VERSION.SDK_INT >= 31) { // (API 31) Build.VERSION_CODE.S if (!PermissionHelper.hasPermission(this, BLUETOOTH_CONNECT)) { diff --git a/src/android/BLECommand.java b/src/android/BLECommand.java index e84ba2f7..1ddb4cb4 100644 --- a/src/android/BLECommand.java +++ b/src/android/BLECommand.java @@ -17,13 +17,11 @@ class BLECommand { // BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE // BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT - private CallbackContext callbackContext; - private UUID serviceUUID; - private UUID characteristicUUID; + private final CallbackContext callbackContext; + private final UUID serviceUUID; + private final UUID characteristicUUID; private byte[] data; - private int type; - private int psm; - + private final int type; public BLECommand(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID, int type) { this.callbackContext = callbackContext; @@ -40,19 +38,6 @@ public BLECommand(CallbackContext callbackContext, UUID serviceUUID, UUID charac this.type = type; } - public BLECommand(CallbackContext callbackContext, int psm, int type) { - this.callbackContext = callbackContext; - this.psm = psm; - this.type = type; - } - - public BLECommand(CallbackContext callbackContext, int psm, byte[] data, int type) { - this.callbackContext = callbackContext; - this.psm = psm; - this.data = data; - this.type = type; - } - public int getType() { return type; } @@ -72,6 +57,4 @@ public UUID getCharacteristicUUID() { public byte[] getData() { return data; } - - public int getPSM() { return psm; } } diff --git a/src/android/L2CAPContext.java b/src/android/L2CAPContext.java index f81df043..a9a7de73 100644 --- a/src/android/L2CAPContext.java +++ b/src/android/L2CAPContext.java @@ -3,7 +3,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.os.Build; -import androidx.annotation.RequiresApi; +import androidx.annotation.RequiresPermission; import org.apache.cordova.CallbackContext; import org.apache.cordova.LOG; @@ -34,6 +34,7 @@ public L2CAPContext(BluetoothDevice device, int psm) { this.executor = Executors.newSingleThreadExecutor(); } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public void connectL2cap(CallbackContext callbackContext, boolean secureChannel) { try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { @@ -107,7 +108,6 @@ public void writeL2CapChannel(CallbackContext callbackContext, byte[] data) { } } - @RequiresApi(api = Build.VERSION_CODES.M) private void readL2CapData() { try { final BluetoothSocket lSocket = this.socket; diff --git a/src/android/Peripheral.java b/src/android/Peripheral.java index 43cddb5b..a69ff5f1 100644 --- a/src/android/Peripheral.java +++ b/src/android/Peripheral.java @@ -14,10 +14,10 @@ package com.megster.cordova.ble.central; +import android.annotation.SuppressLint; import android.app.Activity; import android.bluetooth.*; -import android.os.Build; import android.os.Handler; import android.util.Base64; import org.apache.cordova.CallbackContext; @@ -51,14 +51,14 @@ public class Peripheral extends BluetoothGattCallback { private static final int FAKE_PERIPHERAL_RSSI = 0x7FFFFFFF; - private BluetoothDevice device; + private final BluetoothDevice device; private byte[] advertisingData; private Boolean isConnectable = null; private int advertisingRSSI; private boolean autoconnect = false; private boolean connected = false; private boolean connecting = false; - private ConcurrentLinkedQueue commandQueue = new ConcurrentLinkedQueue(); + private final ConcurrentLinkedQueue commandQueue = new ConcurrentLinkedQueue(); private final Map l2capContexts = new HashMap(); private final AtomicBoolean bleProcessing = new AtomicBoolean(); @@ -72,7 +72,7 @@ public class Peripheral extends BluetoothGattCallback { private CallbackContext bondStateCallback; private Activity currentActivity; - private Map notificationCallbacks = new HashMap(); + private final Map notificationCallbacks = new HashMap(); public Peripheral(BluetoothDevice device) { @@ -91,6 +91,7 @@ public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord this.isConnectable = isConnectable; } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") private void gattConnect() { closeGatt(); @@ -100,14 +101,10 @@ private void gattConnect() { callbackCleanup("Aborted by new connect call"); BluetoothDevice device = getDevice(); - if (Build.VERSION.SDK_INT < 23) { - gatt = device.connectGatt(currentActivity, autoconnect, this); - } else { - gatt = device.connectGatt(currentActivity, autoconnect, this, BluetoothDevice.TRANSPORT_LE); - } - + gatt = device.connectGatt(currentActivity, autoconnect, this, BluetoothDevice.TRANSPORT_LE); } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public void connect(CallbackContext callbackContext, Activity activity, boolean auto) { currentActivity = activity; autoconnect = auto; @@ -121,6 +118,7 @@ public void connect(CallbackContext callbackContext, Activity activity, boolean // the app requested the central disconnect from the peripheral // disconnect the gatt, do not call connectCallback.error + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public void disconnect() { connected = false; connecting = false; @@ -133,6 +131,7 @@ public void disconnect() { // the peripheral disconnected // always call connectCallback.error to notify the app + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public void peripheralDisconnected(String message) { connected = false; connecting = false; @@ -148,6 +147,7 @@ public void peripheralDisconnected(String message) { callbackCleanup(message); } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") private void closeGatt() { BluetoothGatt localGatt; synchronized (this) { @@ -161,6 +161,7 @@ private void closeGatt() { } // notify the phone that the peripheral disconnected + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") private void sendDisconnectMessage(String messageContent) { if (connectCallback != null) { JSONObject message = this.asJSONObject(messageContent); @@ -188,16 +189,13 @@ public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { requestMtuCallback = null; } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public void requestMtu(CallbackContext callback, int mtuValue) { LOG.d(TAG, "requestMtu mtu=%d", mtuValue); if (gatt == null) { callback.error("No GATT"); return; } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - callback.error("Android version does not support requestMtu"); - return; - } if (gatt.requestMtu(mtuValue)) { requestMtuCallback = callback; @@ -206,12 +204,11 @@ public void requestMtu(CallbackContext callback, int mtuValue) { } } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public void requestConnectionPriority(int priority) { if (gatt != null) { LOG.d(TAG, "requestConnectionPriority priority=" + priority); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - gatt.requestConnectionPriority(priority); - } + gatt.requestConnectionPriority(priority); } } @@ -219,7 +216,7 @@ public void requestConnectionPriority(int priority) { * Uses reflection to refresh the device cache. This *might* be helpful if a peripheral changes * services or characteristics and does not correctly implement Service Changed 0x2a05 * on Generic Attribute Service 0x1801. - * + *

* Since this uses an undocumented API it's not guaranteed to work. * */ @@ -237,6 +234,7 @@ public void refreshDeviceCache(CallbackContext callback, final long timeoutMilli Handler handler = new Handler(); LOG.d(TAG, "Waiting " + timeoutMillis + " milliseconds before discovering services"); handler.postDelayed(new Runnable() { + @SuppressLint("MissingPermission") @Override public void run() { if (gatt != null) { @@ -266,6 +264,7 @@ public boolean isUnscanned() { return advertisingData == null; } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public JSONObject asJSONObject() { JSONObject json = new JSONObject(); @@ -291,6 +290,7 @@ public JSONObject asJSONObject() { return json; } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public JSONObject asJSONObject(String errorMessage) { JSONObject json = new JSONObject(); @@ -306,6 +306,7 @@ public JSONObject asJSONObject(String errorMessage) { return json; } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public JSONObject asJSONObject(BluetoothGatt gatt) { JSONObject json = asJSONObject(); @@ -381,6 +382,7 @@ public BluetoothDevice getDevice() { return device; } + @SuppressLint("MissingPermission") @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { super.onServicesDiscovered(gatt, status); @@ -408,6 +410,7 @@ public void onServicesDiscovered(BluetoothGatt gatt, int status) { } } + @SuppressLint("MissingPermission") @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { @@ -537,6 +540,7 @@ public void updateRssi(int rssi) { } // This seems way too complicated + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") private void registerNotifyCallback(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID) { if (gatt == null) { @@ -599,6 +603,7 @@ private void registerNotifyCallback(CallbackContext callbackContext, UUID servic } } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") private void removeNotifyCallback(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID) { if (gatt == null) { @@ -676,6 +681,7 @@ private BluetoothGattCharacteristic findNotifyCharacteristic(BluetoothGattServic return characteristic; } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") private void readCharacteristic(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID) { if (gatt == null) { @@ -718,6 +724,7 @@ private void readCharacteristic(CallbackContext callbackContext, UUID serviceUUI } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") private void readRSSI(CallbackContext callbackContext) { if (gatt == null) { @@ -768,6 +775,7 @@ private BluetoothGattCharacteristic findReadableCharacteristic(BluetoothGattServ return characteristic; } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") private void writeCharacteristic(CallbackContext callbackContext, UUID serviceUUID, UUID characteristicUUID, byte[] data, int writeType) { if (gatt == null) { @@ -887,6 +895,7 @@ public void writeL2CapChannel(CallbackContext callbackContext, int psm, byte[] d getOrAddL2CAPContext(psm).writeL2CapChannel(callbackContext, data); } + @SuppressLint("MissingPermission") private void callbackCleanup(String message) { synchronized(this) { if (readCallback != null) { @@ -923,6 +932,7 @@ private void queueCommand(BLECommand command) { } // command finished, queue the next command + @SuppressLint("MissingPermission") private void commandCompleted() { LOG.d(TAG,"Processing Complete"); bleProcessing.set(false); @@ -930,6 +940,7 @@ private void commandCompleted() { } // process the queue + @SuppressLint("MissingPermission") private void processCommands() { final boolean canProcess = bleProcessing.compareAndSet(false, true); if (!canProcess) { return; } @@ -975,6 +986,7 @@ private String generateHashKey(UUID serviceUUID, BluetoothGattCharacteristic cha return serviceUUID + "|" + characteristic.getUuid() + "|" + characteristic.getInstanceId(); } + @RequiresPermission("android.permission.BLUETOOTH_CONNECT") public void connectL2cap(CallbackContext callbackContext, int psm, boolean secureChannel) { getOrAddL2CAPContext(psm).connectL2cap(callbackContext, secureChannel); } @@ -1009,7 +1021,7 @@ private L2CAPContext getOrAddL2CAPContext(int psm) { } } - @RequiresPermission("android.permission.BLUETOOTH_CONNECT") + @RequiresPermission(allOf = { "android.permission.BLUETOOTH_CONNECT", "android.permission.BLUETOOTH_SCAN" }) public void bond(CallbackContext callbackContext, BluetoothAdapter bluetoothAdapter, boolean usePairingDialog) { if (bondStateCallback != null) { bondStateCallback.error("Aborted by new bond call"); From a5a15be106db7c2fbd79455e578533005d84d6ca Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 13 Jul 2024 20:38:10 +1000 Subject: [PATCH 71/86] Mark broadcast receivers as exported for Android v34 (#1020) --- src/android/BLECentralPlugin.java | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index bd039cb7..a3692085 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -665,7 +665,7 @@ public void onReceive(Context context, Intent intent) { try { IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); - webView.getContext().registerReceiver(this.stateReceiver, intentFilter); + registerSystemReceiverCompat(this.stateReceiver, intentFilter); } catch (Exception e) { LOG.e(TAG, "Error registering state receiver: " + e.getMessage(), e); } @@ -712,7 +712,7 @@ public void onReceive(Context context, Intent intent) { try { IntentFilter intentFilter = new IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION); intentFilter.addAction(Intent.ACTION_PROVIDER_CHANGED); - webView.getContext().registerReceiver(this.locationStateReceiver, intentFilter); + registerSystemReceiverCompat(this.locationStateReceiver, intentFilter); } catch (Exception e) { LOG.e(TAG, "Error registering location state receiver: " + e.getMessage(), e); } @@ -853,7 +853,7 @@ public void onReceive(Context context, Intent intent) { IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - webView.getContext().registerReceiver(broadCastReceiver, intentFilter); + registerSystemReceiverCompat(broadCastReceiver, intentFilter); callbackContext.success("OK"); } catch (Exception e) { @@ -1164,7 +1164,7 @@ public void onScanResult(int callbackType, ScanResult result) { if (!alreadyReported) { Boolean isConnectable = null; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - isConnectable = result.isConnectable(); + isConnectable = result.isConnectable(); } Peripheral peripheral = new Peripheral(device, result.getRssi(), result.getScanRecord().getBytes(), isConnectable); @@ -1511,7 +1511,7 @@ public void onReceive(Context context, Intent intent) { } } }; - webView.getContext().registerReceiver(bondStateReceiver, new IntentFilter(ACTION_BOND_STATE_CHANGED)); + registerSystemReceiverCompat(bondStateReceiver, new IntentFilter(ACTION_BOND_STATE_CHANGED)); } } @@ -1521,4 +1521,14 @@ private void removeBondStateListener() { bondStateReceiver = null; } } + + @SuppressLint({"UnspecifiedRegisterReceiverFlag", "WrongConstant"}) + private void registerSystemReceiverCompat(BroadcastReceiver receiver, IntentFilter filter) { + Context context = webView.getContext(); + if (Build.VERSION.SDK_INT >= 34 /*14*/) { + context.registerReceiver(receiver, filter, 2); // // Context.RECEIVER_EXPORTED + } else { + context.registerReceiver(receiver, filter); + } + } } From 82c65dff61febcf1f33309fa44559db8abb75952 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sun, 14 Jul 2024 17:22:44 +1000 Subject: [PATCH 72/86] Fix incorrect string comparison on Android background location --- src/android/BLECentralPlugin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index a3692085..7a693e5c 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -1228,7 +1228,7 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic missingPermissions.add(Manifest.permission.ACCESS_FINE_LOCATION); } else { String accessBackgroundLocation = this.preferences.getString("accessBackgroundLocation", "false"); - if (accessBackgroundLocation == "true" && !PermissionHelper.hasPermission(this, ACCESS_BACKGROUND_LOCATION)) { + if (accessBackgroundLocation.equals("true") && !PermissionHelper.hasPermission(this, ACCESS_BACKGROUND_LOCATION)) { LOG.w(TAG, "ACCESS_BACKGROUND_LOCATION is being requested"); missingPermissions.add(ACCESS_BACKGROUND_LOCATION); } @@ -1239,7 +1239,7 @@ private void findLowEnergyDevices(CallbackContext callbackContext, UUID[] servic } String accessBackgroundLocation = this.preferences.getString("accessBackgroundLocation", "false"); - if (accessBackgroundLocation == "true" && !PermissionHelper.hasPermission(this, ACCESS_BACKGROUND_LOCATION)) { + if (accessBackgroundLocation.equals("true") && !PermissionHelper.hasPermission(this, ACCESS_BACKGROUND_LOCATION)) { LOG.w(TAG, "ACCESS_BACKGROUND_LOCATION is being requested"); missingPermissions.add(ACCESS_BACKGROUND_LOCATION); } From 3eedb6463236921e71e017fcb00276e18e5f869e Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sun, 14 Jul 2024 20:12:01 +1000 Subject: [PATCH 73/86] Prepare for 1.7.5 release --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c88c4a29..6c46c1bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 1.7.5 + +- iOS: Improve input validation of UUIDs on iOS to avoid crashes (#1014, #905) +- iOS: Address iOS deprecation warnings & general warnings (#919) +- iOS: Harden up iOS service discovery (#741) +- Android: Tidy up some warnings +- Android: Mark broadcast receivers as exported for Android v34 (#1020) + ## 1.7.4 - More cleanly support isConnected usage with a bool return value #1018 with review from @MaximBelov From 8e7e07859cd7fe7e84184848978edbc929ae64f5 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sun, 14 Jul 2024 20:12:28 +1000 Subject: [PATCH 74/86] 1.7.5-alpha.0 --- package.json | 2 +- plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 65ba2752..9b7ce430 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.7.4", + "version": "1.7.5-alpha.0", "description": "Bluetooth Low Energy (BLE) Central Plugin", "scripts": { "slimify": "node tools/make-slim-variant.mjs", diff --git a/plugin.xml b/plugin.xml index a8a9888f..8bf5a525 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin From 956b605dd6bb9c79e60cf59db3cfc47fb6998bdf Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 23 Jul 2024 13:18:10 +1000 Subject: [PATCH 75/86] Only mark export/non-export for non-system receivers on Android v34 (#1020) A clear exception has been carved out for system broadcasts registered via Context#registerReceiver See https://developer.android.com/about/versions/14/behavior-changes-14#system-broadcasts --- src/android/BLECentralPlugin.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/android/BLECentralPlugin.java b/src/android/BLECentralPlugin.java index 7a693e5c..a59de0c9 100644 --- a/src/android/BLECentralPlugin.java +++ b/src/android/BLECentralPlugin.java @@ -665,7 +665,7 @@ public void onReceive(Context context, Intent intent) { try { IntentFilter intentFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); - registerSystemReceiverCompat(this.stateReceiver, intentFilter); + webView.getContext().registerReceiver(this.stateReceiver, intentFilter); } catch (Exception e) { LOG.e(TAG, "Error registering state receiver: " + e.getMessage(), e); } @@ -712,7 +712,7 @@ public void onReceive(Context context, Intent intent) { try { IntentFilter intentFilter = new IntentFilter(LocationManager.PROVIDERS_CHANGED_ACTION); intentFilter.addAction(Intent.ACTION_PROVIDER_CHANGED); - registerSystemReceiverCompat(this.locationStateReceiver, intentFilter); + registerNonSystemReceiverCompat(this.locationStateReceiver, intentFilter); } catch (Exception e) { LOG.e(TAG, "Error registering location state receiver: " + e.getMessage(), e); } @@ -853,7 +853,7 @@ public void onReceive(Context context, Intent intent) { IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST); intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - registerSystemReceiverCompat(broadCastReceiver, intentFilter); + webView.getContext().registerReceiver(broadCastReceiver, intentFilter); callbackContext.success("OK"); } catch (Exception e) { @@ -1511,7 +1511,7 @@ public void onReceive(Context context, Intent intent) { } } }; - registerSystemReceiverCompat(bondStateReceiver, new IntentFilter(ACTION_BOND_STATE_CHANGED)); + webView.getContext().registerReceiver(bondStateReceiver, new IntentFilter(ACTION_BOND_STATE_CHANGED)); } } @@ -1523,7 +1523,7 @@ private void removeBondStateListener() { } @SuppressLint({"UnspecifiedRegisterReceiverFlag", "WrongConstant"}) - private void registerSystemReceiverCompat(BroadcastReceiver receiver, IntentFilter filter) { + private void registerNonSystemReceiverCompat(BroadcastReceiver receiver, IntentFilter filter) { Context context = webView.getContext(); if (Build.VERSION.SDK_INT >= 34 /*14*/) { context.registerReceiver(receiver, filter, 2); // // Context.RECEIVER_EXPORTED From 7984913436429c850c1c5db25e86053d53530073 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 23 Jul 2024 13:43:05 +1000 Subject: [PATCH 76/86] 1.7.5-alpha.1 --- package.json | 2 +- plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9b7ce430..7037cec3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.7.5-alpha.0", + "version": "1.7.5-alpha.1", "description": "Bluetooth Low Energy (BLE) Central Plugin", "scripts": { "slimify": "node tools/make-slim-variant.mjs", diff --git a/plugin.xml b/plugin.xml index 8bf5a525..16e58a19 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin From 748b89d4c8deb591209f5937a6e19c7121831c53 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sat, 31 Aug 2024 09:00:12 +1000 Subject: [PATCH 77/86] 1.7.5 --- package.json | 2 +- plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7037cec3..3b9c7d13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.7.5-alpha.1", + "version": "1.7.5", "description": "Bluetooth Low Energy (BLE) Central Plugin", "scripts": { "slimify": "node tools/make-slim-variant.mjs", diff --git a/plugin.xml b/plugin.xml index 16e58a19..52b6d93f 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin From ed3163da59b6da0c3008ef2f883865a9de36cba0 Mon Sep 17 00:00:00 2001 From: Martin Bech Date: Fri, 30 Aug 2024 14:08:37 +0200 Subject: [PATCH 78/86] Update ble.js with require cordova.exec --- www/ble.js | 82 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/www/ble.js b/www/ble.js index 066731eb..8e6e0d57 100644 --- a/www/ble.js +++ b/www/ble.js @@ -15,6 +15,8 @@ /* global cordova, module */ 'use strict'; +const exec = require('cordova/exec'); + var stringToArrayBuffer = function (str) { var ret = new Uint8Array(str.length); for (var i = 0; i < str.length; i++) { @@ -59,7 +61,7 @@ module.exports = { }, stopScan: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'stopScan', []); + exec(success, failure, 'BLE', 'stopScan', []); }, startScanWithOptions: function (services, options, success, failure) { @@ -68,26 +70,26 @@ module.exports = { success(peripheral); }; options = options || {}; - cordova.exec(successWrapper, failure, 'BLE', 'startScanWithOptions', [services, options]); + exec(successWrapper, failure, 'BLE', 'startScanWithOptions', [services, options]); }, // iOS only connectedPeripheralsWithServices: function (services, success, failure) { - cordova.exec(success, failure, 'BLE', 'connectedPeripheralsWithServices', [services]); + exec(success, failure, 'BLE', 'connectedPeripheralsWithServices', [services]); }, // iOS only peripheralsWithIdentifiers: function (identifiers, success, failure) { - cordova.exec(success, failure, 'BLE', 'peripheralsWithIdentifiers', [identifiers]); + exec(success, failure, 'BLE', 'peripheralsWithIdentifiers', [identifiers]); }, // Android only bondedDevices: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'bondedDevices', []); + exec(success, failure, 'BLE', 'bondedDevices', []); }, list: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'list', []); + exec(success, failure, 'BLE', 'list', []); }, connect: function (device_id, success, failure) { @@ -96,7 +98,7 @@ module.exports = { convertToNativeJS(peripheral); success(peripheral); }; - cordova.exec(successWrapper, failure, 'BLE', 'connect', [device_id]); + exec(successWrapper, failure, 'BLE', 'connect', [device_id]); }, autoConnect: function (deviceId, connectCallback, disconnectCallback) { @@ -117,7 +119,7 @@ module.exports = { // reconnect if we have a peripheral.id and the user didn't call disconnect if (peripheral.id && autoconnected[peripheral.id]) { - cordova.exec(connectCallbackWrapper, disconnectCallbackWrapper, 'BLE', 'autoConnect', [deviceId]); + exec(connectCallbackWrapper, disconnectCallbackWrapper, 'BLE', 'autoConnect', [deviceId]); } }; } else { @@ -125,7 +127,7 @@ module.exports = { disconnectCallbackWrapper = disconnectCallback; } - cordova.exec(connectCallbackWrapper, disconnectCallbackWrapper, 'BLE', 'autoConnect', [deviceId]); + exec(connectCallbackWrapper, disconnectCallbackWrapper, 'BLE', 'autoConnect', [deviceId]); }, disconnect: function (device_id, success, failure) { @@ -134,23 +136,23 @@ module.exports = { } catch (e) { // ignore error } - cordova.exec(success, failure, 'BLE', 'disconnect', [device_id]); + exec(success, failure, 'BLE', 'disconnect', [device_id]); }, queueCleanup: function (device_id, success, failure) { - cordova.exec(success, failure, 'BLE', 'queueCleanup', [device_id]); + exec(success, failure, 'BLE', 'queueCleanup', [device_id]); }, setPin: function (pin, success, failure) { - cordova.exec(success, failure, 'BLE', 'setPin', [pin]); + exec(success, failure, 'BLE', 'setPin', [pin]); }, requestMtu: function (device_id, mtu, success, failure) { - cordova.exec(success, failure, 'BLE', 'requestMtu', [device_id, mtu]); + exec(success, failure, 'BLE', 'requestMtu', [device_id, mtu]); }, requestConnectionPriority: function (device_id, connectionPriority, success, failure) { - cordova.exec(success, failure, 'BLE', 'requestConnectionPriority', [device_id, connectionPriority]); + exec(success, failure, 'BLE', 'requestConnectionPriority', [device_id, connectionPriority]); }, refreshDeviceCache: function (deviceId, timeoutMillis, success, failure) { @@ -158,27 +160,27 @@ module.exports = { convertToNativeJS(peripheral); success(peripheral); }; - cordova.exec(successWrapper, failure, 'BLE', 'refreshDeviceCache', [deviceId, timeoutMillis]); + exec(successWrapper, failure, 'BLE', 'refreshDeviceCache', [deviceId, timeoutMillis]); }, // characteristic value comes back as ArrayBuffer in the success callback read: function (device_id, service_uuid, characteristic_uuid, success, failure) { - cordova.exec(success, failure, 'BLE', 'read', [device_id, service_uuid, characteristic_uuid]); + exec(success, failure, 'BLE', 'read', [device_id, service_uuid, characteristic_uuid]); }, // RSSI value comes back as an integer readRSSI: function (device_id, success, failure) { - cordova.exec(success, failure, 'BLE', 'readRSSI', [device_id]); + exec(success, failure, 'BLE', 'readRSSI', [device_id]); }, // value must be an ArrayBuffer write: function (device_id, service_uuid, characteristic_uuid, value, success, failure) { - cordova.exec(success, failure, 'BLE', 'write', [device_id, service_uuid, characteristic_uuid, value]); + exec(success, failure, 'BLE', 'write', [device_id, service_uuid, characteristic_uuid, value]); }, // value must be an ArrayBuffer writeWithoutResponse: function (device_id, service_uuid, characteristic_uuid, value, success, failure) { - cordova.exec(success, failure, 'BLE', 'writeWithoutResponse', [ + exec(success, failure, 'BLE', 'writeWithoutResponse', [ device_id, service_uuid, characteristic_uuid, @@ -189,7 +191,7 @@ module.exports = { // value must be an ArrayBuffer writeCommand: function (device_id, service_uuid, characteristic_uuid, value, success, failure) { console.log('WARNING: writeCommand is deprecated, use writeWithoutResponse'); - cordova.exec(success, failure, 'BLE', 'writeWithoutResponse', [ + exec(success, failure, 'BLE', 'writeWithoutResponse', [ device_id, service_uuid, characteristic_uuid, @@ -200,7 +202,7 @@ module.exports = { // success callback is called on notification notify: function (device_id, service_uuid, characteristic_uuid, success, failure) { console.log('WARNING: notify is deprecated, use startNotification'); - cordova.exec(success, failure, 'BLE', 'startNotification', [device_id, service_uuid, characteristic_uuid]); + exec(success, failure, 'BLE', 'startNotification', [device_id, service_uuid, characteristic_uuid]); }, // success callback is called on notification @@ -214,65 +216,65 @@ module.exports = { success(data); } } - cordova.exec(onEvent, failure, 'BLE', 'startNotification', [device_id, service_uuid, characteristic_uuid]); + exec(onEvent, failure, 'BLE', 'startNotification', [device_id, service_uuid, characteristic_uuid]); }, // success callback is called when the descriptor 0x2902 is written stopNotification: function (device_id, service_uuid, characteristic_uuid, success, failure) { - cordova.exec(success, failure, 'BLE', 'stopNotification', [device_id, service_uuid, characteristic_uuid]); + exec(success, failure, 'BLE', 'stopNotification', [device_id, service_uuid, characteristic_uuid]); }, isConnected: function (device_id, success, failure) { - cordova.exec(success, failure, 'BLE', 'isConnected', [device_id]); + exec(success, failure, 'BLE', 'isConnected', [device_id]); }, isEnabled: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'isEnabled', []); + exec(success, failure, 'BLE', 'isEnabled', []); }, // Android only isLocationEnabled: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'isLocationEnabled', []); + exec(success, failure, 'BLE', 'isLocationEnabled', []); }, enable: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'enable', []); + exec(success, failure, 'BLE', 'enable', []); }, showBluetoothSettings: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'showBluetoothSettings', []); + exec(success, failure, 'BLE', 'showBluetoothSettings', []); }, startLocationStateNotifications: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'startLocationStateNotifications', []); + exec(success, failure, 'BLE', 'startLocationStateNotifications', []); }, stopLocationStateNotifications: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'stopLocationStateNotifications', []); + exec(success, failure, 'BLE', 'stopLocationStateNotifications', []); }, startStateNotifications: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'startStateNotifications', []); + exec(success, failure, 'BLE', 'startStateNotifications', []); }, stopStateNotifications: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'stopStateNotifications', []); + exec(success, failure, 'BLE', 'stopStateNotifications', []); }, restoredBluetoothState: function (success, failure) { - cordova.exec(success, failure, 'BLE', 'restoredBluetoothState', []); + exec(success, failure, 'BLE', 'restoredBluetoothState', []); }, bond: function (device_id, success, failure, options) { - cordova.exec(success, failure, 'BLE', 'bond', [device_id, options || {}]); + exec(success, failure, 'BLE', 'bond', [device_id, options || {}]); }, unbond: function (device_id, success, failure) { - cordova.exec(success, failure, 'BLE', 'unbond', [device_id]); + exec(success, failure, 'BLE', 'unbond', [device_id]); }, readBondState: function (device_id, success, failure) { - cordova.exec(success, failure, 'BLE', 'readBondState', [device_id]); + exec(success, failure, 'BLE', 'readBondState', [device_id]); }, }; @@ -481,7 +483,7 @@ module.exports.withPromises = { module.exports.l2cap = { close(device_id, psm, success, failure) { - cordova.exec(success, failure, 'BLE', 'closeL2Cap', [device_id, psm]); + exec(success, failure, 'BLE', 'closeL2Cap', [device_id, psm]); }, open(device_id, psmOrOptions, connectCallback, disconnectCallback) { @@ -491,15 +493,15 @@ module.exports.l2cap = { psm = psmOrOptions.psm; settings = psmOrOptions; } - cordova.exec(connectCallback, disconnectCallback, 'BLE', 'openL2Cap', [device_id, psm, settings]); + exec(connectCallback, disconnectCallback, 'BLE', 'openL2Cap', [device_id, psm, settings]); }, receiveData(device_id, psm, receive) { - cordova.exec(receive, function () {}, 'BLE', 'receiveDataL2Cap', [device_id, psm]); + exec(receive, function () {}, 'BLE', 'receiveDataL2Cap', [device_id, psm]); }, write(device_id, psm, data, success, failure) { - cordova.exec(success, failure, 'BLE', 'writeL2Cap', [device_id, psm, data]); + exec(success, failure, 'BLE', 'writeL2Cap', [device_id, psm, data]); }, }; From 94a966d2609035a6e835d3d2b067db2d6805f0e0 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sun, 8 Sep 2024 16:07:05 +1000 Subject: [PATCH 79/86] Prepare for 1.7.6 release --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c46c1bd..d60182d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.7.6 + +- Update ble.js to use require format to improve loading reliability (#1030, #1029) thanks @sithwarrior + ## 1.7.5 - iOS: Improve input validation of UUIDs on iOS to avoid crashes (#1014, #905) From fd5577df7e1728f5b0545ce032b13332ad0770fd Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Sun, 8 Sep 2024 16:07:11 +1000 Subject: [PATCH 80/86] 1.7.6 --- package.json | 2 +- plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3b9c7d13..5c4b0c27 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.7.5", + "version": "1.7.6", "description": "Bluetooth Low Energy (BLE) Central Plugin", "scripts": { "slimify": "node tools/make-slim-variant.mjs", diff --git a/plugin.xml b/plugin.xml index 52b6d93f..cfdf7d78 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin From 0827dca729818131252b8ab52fad09afda384f50 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 10 Sep 2024 10:21:27 +1000 Subject: [PATCH 81/86] Fix support for 16 & 32-bit UUIDs on iOS (#1031) Additional UUID validation on iOS inadvertantly prevented 16 & 32-bit UUID formats from being accepted. --- src/ios/BLECentralPlugin.m | 64 +++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 18 deletions(-) diff --git a/src/ios/BLECentralPlugin.m b/src/ios/BLECentralPlugin.m index 086de15c..a6fc90cf 100644 --- a/src/ios/BLECentralPlugin.m +++ b/src/ios/BLECentralPlugin.m @@ -1110,6 +1110,21 @@ -(NSUUID*) getUUID:(CDVInvokedUrlCommand*)command argumentAtIndex:(NSUInteger)in return uuid; } +-(CBUUID*) getCBUUID:(CDVInvokedUrlCommand*)command argumentAtIndex:(NSUInteger)index { + NSLog(@"getCBUUID"); + + NSString *uuidString = [command argumentAtIndex:index withDefault:@"" andClass:[NSString class]]; + CBUUID *uuid = [self uuidStringToCBUUID:uuidString]; + if (uuid == nil) { + NSString *errorMessage = [NSString stringWithFormat:@"Malformed UUID: %@", [command argumentAtIndex:index]]; + NSLog(@"%@", errorMessage); + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + return nil; + } + return uuid; +} + // expecting deviceUUID, serviceUUID, characteristicUUID in command.arguments -(BLECommandContext*) getData:(CDVInvokedUrlCommand*)command prop:(CBCharacteristicProperties)prop { NSLog(@"getData"); @@ -1121,18 +1136,15 @@ -(BLECommandContext*) getData:(CDVInvokedUrlCommand*)command prop:(CBCharacteris return nil; } - NSUUID *serviceNSUUID = [self getUUID:command argumentAtIndex:1]; - if (serviceNSUUID == nil) { + CBUUID *serviceUUID = [self getCBUUID:command argumentAtIndex:1]; + if (serviceUUID == nil) { return nil; } - NSUUID *characteristicNSUUID = [self getUUID:command argumentAtIndex:2]; - if (characteristicNSUUID == nil) { + CBUUID *characteristicUUID = [self getCBUUID:command argumentAtIndex:2]; + if (characteristicUUID == nil) { return nil; } - - CBUUID *serviceUUID = [CBUUID UUIDWithNSUUID:serviceNSUUID]; - CBUUID *characteristicUUID = [CBUUID UUIDWithNSUUID:characteristicNSUUID]; CBPeripheral *peripheral = [self findPeripheralByUUID:deviceUUID]; @@ -1151,7 +1163,7 @@ -(BLECommandContext*) getData:(CDVInvokedUrlCommand*)command prop:(CBCharacteris if (!service) { NSString *errorMessage = [NSString stringWithFormat:@"Could not find service with UUID %@ on peripheral with UUID %@", - serviceNSUUID.UUIDString, + serviceUUID.UUIDString, peripheral.identifier.UUIDString]; NSLog(@"%@", errorMessage); pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; @@ -1175,8 +1187,8 @@ -(BLECommandContext*) getData:(CDVInvokedUrlCommand*)command prop:(CBCharacteris if (!characteristic) { NSString *errorMessage = [NSString stringWithFormat: @"Could not find characteristic with UUID %@ on service with UUID %@ on peripheral with UUID %@", - characteristicNSUUID.UUIDString, - serviceNSUUID.UUIDString, + characteristicUUID.UUIDString, + serviceUUID.UUIDString, peripheral.identifier.UUIDString]; NSLog(@"%@", errorMessage); pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:errorMessage]; @@ -1281,22 +1293,38 @@ - (NSString*) centralManagerStateToString: (CBManagerState)state { return @"Unknown state"; } +- (CBUUID *) uuidStringToCBUUID: (id)uuidString { + if (![uuidString isKindOfClass:[NSString class]]) { + NSLog(@"Malformed UUID found: %@", uuidString); + return nil; + } + + if ([uuidString length] == 4 || [uuidString length] == 8) { + // For 16 & 32-bit uuids, attempt to convert directly + // This throws an unhandled internal inconsistency error if the format is not right + // that will crash the app + return [CBUUID UUIDWithString:uuidString]; + } + + NSUUID *nsuuid = [[NSUUID alloc]initWithUUIDString:uuidString]; + if (nsuuid == nil) { + NSLog(@"Malformed UUID found: %@", uuidString); + return nil; + } + + return [CBUUID UUIDWithNSUUID:nsuuid]; +} + - (NSArray *) uuidStringsToCBUUIDs: (NSArray *)uuidStrings { NSMutableArray *uuids = [NSMutableArray new]; for (int i = 0; i < [uuidStrings count]; i++) { NSString *uuidString = [uuidStrings objectAtIndex: i]; - if (![uuidString isKindOfClass:[NSString class]]) { - NSLog(@"Malformed UUID found: %@", uuidString); - return nil; - } - NSUUID *nsuuid = [[NSUUID alloc]initWithUUIDString:uuidString]; - if (nsuuid == nil) { - NSLog(@"Malformed UUID found: %@", uuidString); + CBUUID *uuid = [self uuidStringToCBUUID:uuidString]; + if (uuid == nil) { return nil; } - CBUUID *uuid = [CBUUID UUIDWithNSUUID:nsuuid]; [uuids addObject:uuid]; } return uuids; From 45a79bba52fbf1cb16622a18b2d89e851650da11 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 10 Sep 2024 10:48:12 +1000 Subject: [PATCH 82/86] Prepare for 1.7.7 release --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d60182d1..d5df1982 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.7.7 + +- iOS: Fix support for 16 & 32-bit UUIDs on iOS (#1031, #1032) + ## 1.7.6 - Update ble.js to use require format to improve loading reliability (#1030, #1029) thanks @sithwarrior From b5518ca29becb286d748095c98c7fe6d3f4335fc Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Tue, 10 Sep 2024 10:48:26 +1000 Subject: [PATCH 83/86] 1.7.7 --- package.json | 2 +- plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5c4b0c27..f382665e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.7.6", + "version": "1.7.7", "description": "Bluetooth Low Energy (BLE) Central Plugin", "scripts": { "slimify": "node tools/make-slim-variant.mjs", diff --git a/plugin.xml b/plugin.xml index cfdf7d78..1a7e8069 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin From 4c3f0eae850ecf717d1739acd8ecc81b4fb1ff16 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Thu, 12 Sep 2024 10:15:28 +1000 Subject: [PATCH 84/86] Properly convert peripheral UUID for isConnected on iOS (#1033) --- src/ios/BLECentralPlugin.m | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/ios/BLECentralPlugin.m b/src/ios/BLECentralPlugin.m index a6fc90cf..ace165c9 100644 --- a/src/ios/BLECentralPlugin.m +++ b/src/ios/BLECentralPlugin.m @@ -430,9 +430,13 @@ - (void)stopScan:(CDVInvokedUrlCommand*)command { - (void)isConnected:(CDVInvokedUrlCommand*)command { + NSUUID *uuid = [self getUUID:command argumentAtIndex:0]; + if (uuid == nil) { + return; + } + + CBPeripheral *peripheral = [self findPeripheralByUUID:uuid]; CDVPluginResult *pluginResult = nil; - CBPeripheral *peripheral = [self findPeripheralByUUID:[command argumentAtIndex:0]]; - if (peripheral && peripheral.state == CBPeripheralStateConnected) { pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK]; } else { From cd5c37cbff56e008e5dab9b3e6f0711bfdd836d1 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Thu, 12 Sep 2024 10:24:34 +1000 Subject: [PATCH 85/86] Prepare for 1.7.8 release --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5df1982..70bb49fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.7.8 + +- iOS: Properly convert peripheral UUID for isConnected (#1033) + ## 1.7.7 - iOS: Fix support for 16 & 32-bit UUIDs on iOS (#1031, #1032) From 860455c0bf4af099fe44fd8c7dcfec1f924c1162 Mon Sep 17 00:00:00 2001 From: Philip Peitsch Date: Thu, 12 Sep 2024 10:24:40 +1000 Subject: [PATCH 86/86] 1.7.8 --- package.json | 2 +- plugin.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f382665e..5b943076 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cordova-plugin-ble-central", - "version": "1.7.7", + "version": "1.7.8", "description": "Bluetooth Low Energy (BLE) Central Plugin", "scripts": { "slimify": "node tools/make-slim-variant.mjs", diff --git a/plugin.xml b/plugin.xml index 1a7e8069..71a8ddc5 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,5 +1,5 @@ - + BLE Bluetooth Low Energy (BLE) Central Plugin