react-native-ble-manager 8.8.0 → 9.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +131 -44
  2. package/android/.gradle/7.4/checksums/checksums.lock +0 -0
  3. package/android/.gradle/7.4/dependencies-accessors/dependencies-accessors.lock +0 -0
  4. package/android/.gradle/7.4/dependencies-accessors/gc.properties +0 -0
  5. package/android/.gradle/7.4/executionHistory/executionHistory.bin +0 -0
  6. package/android/.gradle/7.4/executionHistory/executionHistory.lock +0 -0
  7. package/android/.gradle/7.4/fileChanges/last-build.bin +0 -0
  8. package/android/.gradle/7.4/fileHashes/fileHashes.bin +0 -0
  9. package/android/.gradle/7.4/fileHashes/fileHashes.lock +0 -0
  10. package/android/.gradle/7.4/fileHashes/resourceHashesCache.bin +0 -0
  11. package/android/.gradle/7.4/gc.properties +0 -0
  12. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  13. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  14. package/android/.gradle/buildOutputCleanup/outputFiles.bin +0 -0
  15. package/android/.gradle/file-system.probe +0 -0
  16. package/android/.gradle/vcs-1/gc.properties +0 -0
  17. package/android/.idea/.name +1 -0
  18. package/android/.idea/compiler.xml +6 -0
  19. package/android/.idea/gradle.xml +18 -0
  20. package/android/.idea/jarRepositories.xml +40 -0
  21. package/android/.idea/misc.xml +10 -0
  22. package/android/.idea/vcs.xml +6 -0
  23. package/android/build.gradle +17 -17
  24. package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  25. package/android/gradle/wrapper/gradle-wrapper.properties +5 -0
  26. package/android/gradle.properties +3 -1
  27. package/android/gradlew +234 -0
  28. package/android/gradlew.bat +89 -0
  29. package/android/local.properties +8 -0
  30. package/android/src/main/java/it/innove/BleManager.java +38 -17
  31. package/android/src/main/java/it/innove/LegacyScanManager.java +33 -28
  32. package/android/src/main/java/it/innove/LollipopPeripheral.java +66 -65
  33. package/android/src/main/java/it/innove/LollipopScanManager.java +52 -25
  34. package/android/src/main/java/it/innove/Peripheral.java +973 -838
  35. package/android/src/main/java/it/innove/ScanManager.java +24 -23
  36. package/dist/cjs/index.d.ts +156 -0
  37. package/dist/cjs/index.js +507 -0
  38. package/dist/cjs/index.js.map +1 -0
  39. package/dist/cjs/types.d.ts +311 -0
  40. package/dist/cjs/types.js +110 -0
  41. package/dist/cjs/types.js.map +1 -0
  42. package/dist/esm/index.d.ts +156 -0
  43. package/dist/esm/index.js +491 -0
  44. package/dist/esm/index.js.map +1 -0
  45. package/dist/esm/types.d.ts +311 -0
  46. package/dist/esm/types.js +107 -0
  47. package/dist/esm/types.js.map +1 -0
  48. package/ios/BleManager.h +1 -0
  49. package/ios/BleManager.m +213 -115
  50. package/ios/BleManager.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  51. package/ios/BleManager.xcodeproj/project.xcworkspace/xcuserdata/marco.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  52. package/ios/BleManager.xcodeproj/xcuserdata/marco.xcuserdatad/xcschemes/xcschememanagement.plist +14 -0
  53. package/ios/CBPeripheral+Extensions.m +1 -1
  54. package/package.json +31 -4
  55. package/src/index.ts +595 -0
  56. package/src/types.ts +336 -0
  57. package/.github/FUNDING.yml +0 -3
  58. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -38
  59. package/BleManager.js +0 -427
  60. package/index.d.ts +0 -294
@@ -1,5 +1,8 @@
1
1
  package it.innove;
2
2
 
3
+ import static android.os.Build.VERSION_CODES.LOLLIPOP;
4
+ import static com.facebook.react.common.ReactConstants.TAG;
5
+
3
6
  import android.app.Activity;
4
7
  import android.bluetooth.BluetoothDevice;
5
8
  import android.bluetooth.BluetoothGatt;
@@ -12,10 +15,11 @@ import android.content.Context;
12
15
  import android.os.Build;
13
16
  import android.os.Handler;
14
17
  import android.os.Looper;
15
- import androidx.annotation.Nullable;
16
18
  import android.util.Base64;
17
19
  import android.util.Log;
18
20
 
21
+ import androidx.annotation.Nullable;
22
+
19
23
  import com.facebook.react.bridge.Arguments;
20
24
  import com.facebook.react.bridge.Callback;
21
25
  import com.facebook.react.bridge.ReactContext;
@@ -27,441 +31,475 @@ import org.json.JSONException;
27
31
 
28
32
  import java.lang.reflect.Method;
29
33
  import java.util.ArrayList;
30
- import java.util.Map;
31
34
  import java.util.Arrays;
32
35
  import java.util.Iterator;
36
+ import java.util.LinkedList;
33
37
  import java.util.List;
34
- import java.util.UUID;
38
+ import java.util.Map;
35
39
  import java.util.Queue;
40
+ import java.util.UUID;
36
41
  import java.util.concurrent.ConcurrentHashMap;
37
42
  import java.util.concurrent.ConcurrentLinkedQueue;
38
43
 
39
- import static android.os.Build.VERSION_CODES.LOLLIPOP;
40
- import static com.facebook.react.common.ReactConstants.TAG;
41
-
42
44
  /**
43
45
  * Peripheral wraps the BluetoothDevice and provides methods to convert to JSON.
44
46
  */
45
47
  public class Peripheral extends BluetoothGattCallback {
46
48
 
47
- private static final String CHARACTERISTIC_NOTIFICATION_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
48
- public static final int GATT_INSUFFICIENT_AUTHENTICATION = 5;
49
- public static final int GATT_AUTH_FAIL = 137;
50
-
51
- private final BluetoothDevice device;
52
- private final Map<String, NotifyBufferContainer> bufferedCharacteristics;
53
- protected volatile byte[] advertisingDataBytes = new byte[0];
54
- protected volatile int advertisingRSSI;
55
- private volatile boolean connected = false;
56
- private volatile boolean connecting = false;
57
- private ReactContext reactContext;
58
-
59
- private BluetoothGatt gatt;
60
-
61
- private Callback connectCallback;
62
- private Callback retrieveServicesCallback;
63
- private Callback readCallback;
64
- private Callback readRSSICallback;
65
- private Callback writeCallback;
66
- private Callback registerNotifyCallback;
67
- private Callback requestMTUCallback;
68
-
69
- private final Queue<Runnable> commandQueue = new ConcurrentLinkedQueue<>();
70
- private final Handler mainHandler = new Handler(Looper.getMainLooper());
71
- private Runnable discoverServicesRunnable;
72
- private boolean commandQueueBusy = false;
73
-
74
- private List<byte[]> writeQueue = new ArrayList<>();
75
-
76
- public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord, ReactContext reactContext) {
77
- this.device = device;
78
- this.bufferedCharacteristics = new ConcurrentHashMap<String, NotifyBufferContainer>();
79
- this.advertisingRSSI = advertisingRSSI;
80
- this.advertisingDataBytes = scanRecord;
81
- this.reactContext = reactContext;
82
- }
83
-
84
- public Peripheral(BluetoothDevice device, ReactContext reactContext) {
85
- this.device = device;
86
- this.bufferedCharacteristics = new ConcurrentHashMap<String, NotifyBufferContainer>();
87
- this.reactContext = reactContext;
88
- }
89
-
90
- private void sendEvent(String eventName, @Nullable WritableMap params) {
91
- synchronized (reactContext) {
92
- reactContext.getJSModule(RCTNativeAppEventEmitter.class).emit(eventName, params);
93
- }
94
- }
95
-
96
- private void sendConnectionEvent(BluetoothDevice device, String eventName, int status) {
97
- WritableMap map = Arguments.createMap();
98
- map.putString("peripheral", device.getAddress());
99
- if (status != -1) {
100
- map.putInt("status", status);
101
- }
102
- sendEvent(eventName, map);
103
- Log.d(BleManager.LOG_TAG, "Peripheral event (" + eventName + "):" + device.getAddress());
104
- }
105
-
106
- public void connect(final Callback callback, Activity activity) {
107
- mainHandler.post(() -> {
108
- if (!connected) {
109
- BluetoothDevice device = getDevice();
110
- this.connectCallback = callback;
111
- this.connecting = true;
112
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
113
- Log.d(BleManager.LOG_TAG, " Is Or Greater than M $mBluetoothDevice");
114
- gatt = device.connectGatt(activity, false, this, BluetoothDevice.TRANSPORT_LE);
115
- } else {
116
- Log.d(BleManager.LOG_TAG, " Less than M");
117
- try {
118
- Log.d(BleManager.LOG_TAG, " Trying TRANPORT LE with reflection");
119
- Method m = device.getClass().getDeclaredMethod("connectGatt", Context.class, Boolean.class,
120
- BluetoothGattCallback.class, Integer.class);
121
- m.setAccessible(true);
122
- Integer transport = device.getClass().getDeclaredField("TRANSPORT_LE").getInt(null);
123
- gatt = (BluetoothGatt) m.invoke(device, activity, false, this, transport);
124
- } catch (Exception e) {
125
- e.printStackTrace();
126
- Log.d(TAG, " Catch to call normal connection");
127
- gatt = device.connectGatt(activity, false, this);
128
- }
129
- }
130
- } else {
131
- if (gatt != null) {
132
- callback.invoke();
133
- } else {
134
- callback.invoke("BluetoothGatt is null");
135
- }
136
- }
137
- });
138
- }
139
- // bt_btif : Register with GATT stack failed.
49
+ private static final String CHARACTERISTIC_NOTIFICATION_CONFIG = "00002902-0000-1000-8000-00805f9b34fb";
50
+ public static final int GATT_INSUFFICIENT_AUTHENTICATION = 5;
51
+ public static final int GATT_AUTH_FAIL = 137;
52
+
53
+ private final BluetoothDevice device;
54
+ private final Map<String, NotifyBufferContainer> bufferedCharacteristics;
55
+ protected volatile byte[] advertisingDataBytes = new byte[0];
56
+ protected volatile int advertisingRSSI;
57
+ private volatile boolean connected = false;
58
+ private volatile boolean connecting = false;
59
+ private ReactContext reactContext;
60
+
61
+ private BluetoothGatt gatt;
62
+
63
+ private LinkedList<Callback> connectCallbacks = new LinkedList<>();
64
+ private LinkedList<Callback> retrieveServicesCallbacks = new LinkedList<>();
65
+ private LinkedList<Callback> readCallbacks = new LinkedList<>();
66
+ private LinkedList<Callback> readDescriptorCallbacks = new LinkedList<>();
67
+ private LinkedList<Callback> readRSSICallbacks = new LinkedList<>();
68
+ private LinkedList<Callback> writeCallbacks = new LinkedList<>();
69
+ private LinkedList<Callback> registerNotifyCallbacks = new LinkedList<>();
70
+ private LinkedList<Callback> requestMTUCallbacks = new LinkedList<>();
71
+
72
+ private final Queue<Runnable> commandQueue = new ConcurrentLinkedQueue<>();
73
+ private final Handler mainHandler = new Handler(Looper.getMainLooper());
74
+ private Runnable discoverServicesRunnable;
75
+ private boolean commandQueueBusy = false;
76
+
77
+ private List<byte[]> writeQueue = new ArrayList<>();
78
+
79
+ public Peripheral(BluetoothDevice device, int advertisingRSSI, byte[] scanRecord, ReactContext reactContext) {
80
+ this.device = device;
81
+ this.bufferedCharacteristics = new ConcurrentHashMap<String, NotifyBufferContainer>();
82
+ this.advertisingRSSI = advertisingRSSI;
83
+ this.advertisingDataBytes = scanRecord;
84
+ this.reactContext = reactContext;
85
+ }
86
+
87
+ public Peripheral(BluetoothDevice device, ReactContext reactContext) {
88
+ this.device = device;
89
+ this.bufferedCharacteristics = new ConcurrentHashMap<String, NotifyBufferContainer>();
90
+ this.reactContext = reactContext;
91
+ }
92
+
93
+ private void sendEvent(String eventName, @Nullable WritableMap params) {
94
+ synchronized (reactContext) {
95
+ reactContext.getJSModule(RCTNativeAppEventEmitter.class).emit(eventName, params);
96
+ }
97
+ }
98
+
99
+ private void sendConnectionEvent(BluetoothDevice device, String eventName, int status) {
100
+ WritableMap map = Arguments.createMap();
101
+ map.putString("peripheral", device.getAddress());
102
+ if (status != -1) {
103
+ map.putInt("status", status);
104
+ }
105
+ sendEvent(eventName, map);
106
+ Log.d(BleManager.LOG_TAG, "Peripheral event (" + eventName + "):" + device.getAddress());
107
+ }
108
+
109
+ public void connect(final Callback callback, Activity activity) {
110
+ mainHandler.post(() -> {
111
+ if (!connected) {
112
+ BluetoothDevice device = getDevice();
113
+ this.connectCallbacks.addLast(callback);
114
+ this.connecting = true;
115
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
116
+ Log.d(BleManager.LOG_TAG, " Is Or Greater than M $mBluetoothDevice");
117
+ gatt = device.connectGatt(activity, false, this, BluetoothDevice.TRANSPORT_LE);
118
+ } else {
119
+ Log.d(BleManager.LOG_TAG, " Less than M");
120
+ try {
121
+ Log.d(BleManager.LOG_TAG, " Trying TRANPORT LE with reflection");
122
+ Method m = device.getClass().getDeclaredMethod("connectGatt", Context.class, Boolean.class,
123
+ BluetoothGattCallback.class, Integer.class);
124
+ m.setAccessible(true);
125
+ Integer transport = device.getClass().getDeclaredField("TRANSPORT_LE").getInt(null);
126
+ gatt = (BluetoothGatt) m.invoke(device, activity, false, this, transport);
127
+ } catch (Exception e) {
128
+ e.printStackTrace();
129
+ Log.d(TAG, " Catch to call normal connection");
130
+ gatt = device.connectGatt(activity, false, this);
131
+ }
132
+ }
133
+ } else {
134
+ if (gatt != null) {
135
+ callback.invoke();
136
+ } else {
137
+ callback.invoke("BluetoothGatt is null");
138
+ }
139
+ }
140
+ });
141
+ }
142
+ // bt_btif : Register with GATT stack failed.
140
143
 
141
144
  public void disconnect(final Callback callback, final boolean force) {
142
145
  mainHandler.post(() -> {
143
- connectCallback = null;
146
+ for (Callback connectCallback: connectCallbacks) {
147
+ connectCallback.invoke("Disconnect called before connect callback invoked");
148
+ }
149
+ connectCallbacks.clear();
144
150
  connected = false;
145
151
  clearBuffers();
146
152
  commandQueue.clear();
147
153
  commandQueueBusy = false;
148
154
 
149
- if (gatt != null) {
150
- try {
151
- gatt.disconnect();
152
- if (force) {
153
- gatt.close();
154
- gatt = null;
155
- sendConnectionEvent(device, "BleManagerDisconnectPeripheral", BluetoothGatt.GATT_SUCCESS);
156
- }
157
- Log.d(BleManager.LOG_TAG, "Disconnect");
158
- } catch (Exception e) {
159
- sendConnectionEvent(device, "BleManagerDisconnectPeripheral", BluetoothGatt.GATT_FAILURE);
160
- Log.d(BleManager.LOG_TAG, "Error on disconnect", e);
161
- }
162
- } else
163
- Log.d(BleManager.LOG_TAG, "GATT is null");
164
- if (callback != null)
165
- callback.invoke();
166
- });
167
- }
168
-
169
- public WritableMap asWritableMap() {
170
- WritableMap map = Arguments.createMap();
171
- WritableMap advertising = Arguments.createMap();
172
-
173
- try {
174
- map.putString("name", device.getName());
175
- map.putString("id", device.getAddress()); // mac address
176
- map.putInt("rssi", advertisingRSSI);
177
-
178
- String name = device.getName();
179
- if (name != null)
180
- advertising.putString("localName", name);
181
-
182
- advertising.putMap("manufacturerData", byteArrayToWritableMap(advertisingDataBytes));
183
-
184
- // No scanResult to access so we can't check if peripheral is connectable
185
- advertising.putBoolean("isConnectable", true);
186
-
187
- map.putMap("advertising", advertising);
188
- } catch (Exception e) { // this shouldn't happen
189
- e.printStackTrace();
190
- }
191
-
192
- return map;
193
- }
194
-
195
- public WritableMap asWritableMap(BluetoothGatt gatt) {
196
-
197
- WritableMap map = asWritableMap();
198
-
199
- WritableArray servicesArray = Arguments.createArray();
200
- WritableArray characteristicsArray = Arguments.createArray();
201
-
202
- if (connected && gatt != null) {
203
- for (Iterator<BluetoothGattService> it = gatt.getServices().iterator(); it.hasNext();) {
204
- BluetoothGattService service = it.next();
205
- WritableMap serviceMap = Arguments.createMap();
206
- serviceMap.putString("uuid", UUIDHelper.uuidToString(service.getUuid()));
207
-
208
- for (Iterator<BluetoothGattCharacteristic> itCharacteristic = service.getCharacteristics()
209
- .iterator(); itCharacteristic.hasNext();) {
210
- BluetoothGattCharacteristic characteristic = itCharacteristic.next();
211
- WritableMap characteristicsMap = Arguments.createMap();
212
-
213
- characteristicsMap.putString("service", UUIDHelper.uuidToString(service.getUuid()));
214
- characteristicsMap.putString("characteristic", UUIDHelper.uuidToString(characteristic.getUuid()));
215
-
216
- characteristicsMap.putMap("properties", Helper.decodeProperties(characteristic));
217
-
218
- if (characteristic.getPermissions() > 0) {
219
- characteristicsMap.putMap("permissions", Helper.decodePermissions(characteristic));
220
- }
221
-
222
- WritableArray descriptorsArray = Arguments.createArray();
223
-
224
- for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
225
- WritableMap descriptorMap = Arguments.createMap();
226
- descriptorMap.putString("uuid", UUIDHelper.uuidToString(descriptor.getUuid()));
227
- if (descriptor.getValue() != null) {
228
- descriptorMap.putString("value",
229
- Base64.encodeToString(descriptor.getValue(), Base64.NO_WRAP));
230
- } else {
231
- descriptorMap.putString("value", null);
232
- }
233
-
234
- if (descriptor.getPermissions() > 0) {
235
- descriptorMap.putMap("permissions", Helper.decodePermissions(descriptor));
236
- }
237
- descriptorsArray.pushMap(descriptorMap);
238
- }
239
- if (descriptorsArray.size() > 0) {
240
- characteristicsMap.putArray("descriptors", descriptorsArray);
241
- }
242
- characteristicsArray.pushMap(characteristicsMap);
243
- }
244
- servicesArray.pushMap(serviceMap);
245
- }
246
- map.putArray("services", servicesArray);
247
- map.putArray("characteristics", characteristicsArray);
248
- }
249
-
250
- return map;
251
- }
252
-
253
- static WritableMap byteArrayToWritableMap(byte[] bytes) throws JSONException {
254
- WritableMap object = Arguments.createMap();
255
- object.putString("CDVType", "ArrayBuffer");
256
- object.putString("data", bytes != null ? Base64.encodeToString(bytes, Base64.NO_WRAP) : null);
257
- object.putArray("bytes", bytes != null ? BleManager.bytesToWritableArray(bytes) : null);
258
- return object;
259
- }
260
-
261
- public boolean isConnected() {
262
- return connected;
263
- }
264
-
265
- public boolean isConnecting() {
266
- return connecting;
267
- }
268
-
269
- public BluetoothDevice getDevice() {
270
- return device;
271
- }
155
+ if (gatt != null) {
156
+ try {
157
+ gatt.disconnect();
158
+ if (force) {
159
+ gatt.close();
160
+ gatt = null;
161
+ sendConnectionEvent(device, "BleManagerDisconnectPeripheral", BluetoothGatt.GATT_SUCCESS);
162
+ }
163
+ Log.d(BleManager.LOG_TAG, "Disconnect");
164
+ } catch (Exception e) {
165
+ sendConnectionEvent(device, "BleManagerDisconnectPeripheral", BluetoothGatt.GATT_FAILURE);
166
+ Log.d(BleManager.LOG_TAG, "Error on disconnect", e);
167
+ }
168
+ } else
169
+ Log.d(BleManager.LOG_TAG, "GATT is null");
170
+ if (callback != null)
171
+ callback.invoke();
172
+ });
173
+ }
174
+
175
+ public WritableMap asWritableMap() {
176
+ WritableMap map = Arguments.createMap();
177
+ WritableMap advertising = Arguments.createMap();
178
+
179
+ try {
180
+ map.putString("name", device.getName());
181
+ map.putString("id", device.getAddress()); // mac address
182
+ map.putInt("rssi", advertisingRSSI);
183
+
184
+ String name = device.getName();
185
+ if (name != null)
186
+ advertising.putString("localName", name);
187
+
188
+ advertising.putMap("manufacturerData", byteArrayToWritableMap(advertisingDataBytes));
189
+
190
+ // No scanResult to access so we can't check if peripheral is connectable
191
+ advertising.putBoolean("isConnectable", true);
192
+
193
+ map.putMap("advertising", advertising);
194
+ } catch (Exception e) { // this shouldn't happen
195
+ e.printStackTrace();
196
+ }
197
+
198
+ return map;
199
+ }
200
+
201
+ public WritableMap asWritableMap(BluetoothGatt gatt) {
202
+
203
+ WritableMap map = asWritableMap();
204
+
205
+ WritableArray servicesArray = Arguments.createArray();
206
+ WritableArray characteristicsArray = Arguments.createArray();
207
+
208
+ if (connected && gatt != null) {
209
+ for (Iterator<BluetoothGattService> it = gatt.getServices().iterator(); it.hasNext(); ) {
210
+ BluetoothGattService service = it.next();
211
+ WritableMap serviceMap = Arguments.createMap();
212
+ serviceMap.putString("uuid", UUIDHelper.uuidToString(service.getUuid()));
213
+
214
+ for (Iterator<BluetoothGattCharacteristic> itCharacteristic = service.getCharacteristics()
215
+ .iterator(); itCharacteristic.hasNext(); ) {
216
+ BluetoothGattCharacteristic characteristic = itCharacteristic.next();
217
+ WritableMap characteristicsMap = Arguments.createMap();
218
+
219
+ characteristicsMap.putString("service", UUIDHelper.uuidToString(service.getUuid()));
220
+ characteristicsMap.putString("characteristic", UUIDHelper.uuidToString(characteristic.getUuid()));
221
+
222
+ characteristicsMap.putMap("properties", Helper.decodeProperties(characteristic));
223
+
224
+ if (characteristic.getPermissions() > 0) {
225
+ characteristicsMap.putMap("permissions", Helper.decodePermissions(characteristic));
226
+ }
227
+
228
+ WritableArray descriptorsArray = Arguments.createArray();
229
+
230
+ for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
231
+ WritableMap descriptorMap = Arguments.createMap();
232
+ descriptorMap.putString("uuid", UUIDHelper.uuidToString(descriptor.getUuid()));
233
+ if (descriptor.getValue() != null) {
234
+ descriptorMap.putString("value",
235
+ Base64.encodeToString(descriptor.getValue(), Base64.NO_WRAP));
236
+ } else {
237
+ descriptorMap.putString("value", null);
238
+ }
239
+
240
+ if (descriptor.getPermissions() > 0) {
241
+ descriptorMap.putMap("permissions", Helper.decodePermissions(descriptor));
242
+ }
243
+ descriptorsArray.pushMap(descriptorMap);
244
+ }
245
+ if (descriptorsArray.size() > 0) {
246
+ characteristicsMap.putArray("descriptors", descriptorsArray);
247
+ }
248
+ characteristicsArray.pushMap(characteristicsMap);
249
+ }
250
+ servicesArray.pushMap(serviceMap);
251
+ }
252
+ map.putArray("services", servicesArray);
253
+ map.putArray("characteristics", characteristicsArray);
254
+ }
255
+
256
+ return map;
257
+ }
258
+
259
+ static WritableMap byteArrayToWritableMap(byte[] bytes) throws JSONException {
260
+ WritableMap object = Arguments.createMap();
261
+ object.putString("CDVType", "ArrayBuffer");
262
+ object.putString("data", bytes != null ? Base64.encodeToString(bytes, Base64.NO_WRAP) : null);
263
+ object.putArray("bytes", bytes != null ? BleManager.bytesToWritableArray(bytes) : null);
264
+ return object;
265
+ }
266
+
267
+ public boolean isConnected() {
268
+ return connected;
269
+ }
270
+
271
+ public boolean isConnecting() {
272
+ return connecting;
273
+ }
274
+
275
+ public BluetoothDevice getDevice() {
276
+ return device;
277
+ }
272
278
 
273
279
  @Override
274
280
  public void onServicesDiscovered(BluetoothGatt gatt, int status) {
275
281
  super.onServicesDiscovered(gatt, status);
276
282
  mainHandler.post(() -> {
277
- if (retrieveServicesCallback != null) {
278
- WritableMap map = this.asWritableMap(gatt);
283
+ WritableMap map = this.asWritableMap(gatt);
284
+ for (Callback retrieveServicesCallback: retrieveServicesCallbacks) {
279
285
  retrieveServicesCallback.invoke(null, map);
280
- retrieveServicesCallback = null;
281
286
  }
287
+ retrieveServicesCallbacks.clear();
282
288
  completedCommand();
283
289
  });
284
290
  }
285
291
 
286
- @Override
287
- public void onConnectionStateChange(BluetoothGatt gatta, int status, final int newState) {
292
+ @Override
293
+ public void onConnectionStateChange(BluetoothGatt gatta, int status, final int newState) {
288
294
 
289
- Log.d(BleManager.LOG_TAG, "onConnectionStateChange to " + newState + " on peripheral: " + device.getAddress()
290
- + " with status " + status);
295
+ Log.d(BleManager.LOG_TAG, "onConnectionStateChange to " + newState + " on peripheral: " + device.getAddress()
296
+ + " with status " + status);
291
297
 
292
- mainHandler.post(() -> {
293
- gatt = gatta;
298
+ mainHandler.post(() -> {
299
+ gatt = gatta;
294
300
 
295
- if (status != BluetoothGatt.GATT_SUCCESS) {
296
- gatt.close();
297
- }
301
+ if (status != BluetoothGatt.GATT_SUCCESS) {
302
+ gatt.close();
303
+ }
298
304
 
299
- connecting = false;
300
- if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
301
- connected = true;
302
-
303
- discoverServicesRunnable = new Runnable() {
304
- @Override
305
- public void run() {
306
- try {
307
- gatt.discoverServices();
308
- } catch (NullPointerException e) {
309
- Log.d(BleManager.LOG_TAG, "onConnectionStateChange connected but gatt of Run method was null");
310
- }
311
- discoverServicesRunnable = null;
312
- }
313
- };
305
+ connecting = false;
306
+ if (newState == BluetoothProfile.STATE_CONNECTED && status == BluetoothGatt.GATT_SUCCESS) {
307
+ connected = true;
308
+
309
+ discoverServicesRunnable = new Runnable() {
310
+ @Override
311
+ public void run() {
312
+ try {
313
+ gatt.discoverServices();
314
+ } catch (NullPointerException e) {
315
+ Log.d(BleManager.LOG_TAG, "onConnectionStateChange connected but gatt of Run method was null");
316
+ }
317
+ discoverServicesRunnable = null;
318
+ }
319
+ };
314
320
 
315
- mainHandler.post(discoverServicesRunnable);
321
+ mainHandler.post(discoverServicesRunnable);
316
322
 
317
- sendConnectionEvent(device, "BleManagerConnectPeripheral", status);
323
+ sendConnectionEvent(device, "BleManagerConnectPeripheral", status);
318
324
 
319
- if (connectCallback != null) {
320
- Log.d(BleManager.LOG_TAG, "Connected to: " + device.getAddress());
325
+ Log.d(BleManager.LOG_TAG, "Connected to: " + device.getAddress());
326
+ for (Callback connectCallback: connectCallbacks) {
321
327
  connectCallback.invoke();
322
- connectCallback = null;
323
328
  }
329
+ connectCallbacks.clear();
324
330
 
325
- } else if (newState == BluetoothProfile.STATE_DISCONNECTED || status != BluetoothGatt.GATT_SUCCESS) {
331
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED || status != BluetoothGatt.GATT_SUCCESS) {
326
332
 
327
- if (discoverServicesRunnable != null) {
328
- mainHandler.removeCallbacks(discoverServicesRunnable);
329
- discoverServicesRunnable = null;
330
- }
333
+ if (discoverServicesRunnable != null) {
334
+ mainHandler.removeCallbacks(discoverServicesRunnable);
335
+ discoverServicesRunnable = null;
336
+ }
331
337
 
332
- List<Callback> callbacks = Arrays.asList(writeCallback, retrieveServicesCallback, readRSSICallback,
333
- readCallback, registerNotifyCallback, requestMTUCallback);
334
- for (Callback currentCallback : callbacks) {
335
- if (currentCallback != null) {
336
- try {
337
- currentCallback.invoke("Device disconnected");
338
- } catch (Exception e) {
339
- e.printStackTrace();
340
- } }
341
- }
342
- if (connectCallback != null) {
343
- connectCallback.invoke("Connection error");
344
- connectCallback = null;
338
+ for (Callback writeCallback: writeCallbacks) {
339
+ writeCallback.invoke("Device disconnected");
345
340
  }
346
- writeCallback = null;
347
- writeQueue.clear();
348
- readCallback = null;
349
- retrieveServicesCallback = null;
350
- readRSSICallback = null;
351
- registerNotifyCallback = null;
352
- requestMTUCallback = null;
353
- commandQueue.clear();
354
- commandQueueBusy = false;
355
- connectCallback = null;
356
- connected = false;
357
- clearBuffers();
358
- commandQueue.clear();
359
- commandQueueBusy = false;
360
-
361
- gatt.disconnect();
362
- gatt.close();
363
- gatt = null;
364
- sendConnectionEvent(device, "BleManagerDisconnectPeripheral", BluetoothGatt.GATT_SUCCESS);
341
+ writeCallbacks.clear();
365
342
 
366
- }
367
-
368
- });
369
-
370
- }
371
-
372
- public void updateRssi(int rssi) {
373
- advertisingRSSI = rssi;
374
- }
343
+ for (Callback retrieveServicesCallback: retrieveServicesCallbacks) {
344
+ retrieveServicesCallback.invoke("Device disconnected");
345
+ }
346
+ retrieveServicesCallbacks.clear();
375
347
 
376
- public void updateData(byte[] data) {
377
- advertisingDataBytes = data;
378
- }
348
+ for (Callback readRSSICallback: readRSSICallbacks) {
349
+ readRSSICallback.invoke("Device disconnected");
350
+ }
351
+ readRSSICallbacks.clear();
379
352
 
380
- public int unsignedToBytes(byte b) {
381
- return b & 0xFF;
382
- }
353
+ for (Callback registerNotifyCallback: registerNotifyCallbacks) {
354
+ registerNotifyCallback.invoke("Device disconnected");
355
+ }
356
+ registerNotifyCallbacks.clear();
383
357
 
384
- //////
358
+ for (Callback requestMTUCallback: requestMTUCallbacks) {
359
+ requestMTUCallback.invoke("Device disconnected");
360
+ }
361
+ requestMTUCallbacks.clear();
385
362
 
386
- @Override
387
- public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
388
- super.onCharacteristicChanged(gatt, characteristic);
389
- try {
390
- String charString = characteristic.getUuid().toString();
391
- String service = characteristic.getService().getUuid().toString();
392
- NotifyBufferContainer buffer = this.bufferedCharacteristics
393
- .get(this.bufferedCharacteristicsKey(service, charString));
394
- byte[] dataValue = characteristic.getValue();
395
- if (buffer != null) {
396
- buffer.put(dataValue);
397
- Log.d(BleManager.LOG_TAG, "onCharacteristicChanged-buffering: " +
398
- buffer.size() + " from peripheral: " + device.getAddress());
399
-
400
- if (buffer.isBufferFull()) {
401
- Log.d(BleManager.LOG_TAG, "onCharacteristicChanged sending buffered data " + buffer.size());
402
-
403
- // send'm and reset
404
- dataValue = buffer.items.array();
405
- buffer.resetBuffer();
406
- } else {
407
- return;
363
+ for (Callback readCallback: readCallbacks) {
364
+ readCallback.invoke("Device disconnected");
408
365
  }
409
- }
410
- Log.d(BleManager.LOG_TAG, "onCharacteristicChanged: " + BleManager.bytesToHex(dataValue)
411
- + " from peripheral: " + device.getAddress());
412
- WritableMap map = Arguments.createMap();
413
- map.putString("peripheral", device.getAddress());
414
- map.putString("characteristic", charString);
415
- map.putString("service", service);
416
- map.putArray("value", BleManager.bytesToWritableArray(dataValue));
417
- sendEvent("BleManagerDidUpdateValueForCharacteristic", map);
418
-
419
- } catch (Exception e) {
420
- Log.d(BleManager.LOG_TAG, "onCharacteristicChanged ERROR: " + e.toString());
421
- }
422
- }
366
+ readCallbacks.clear();
423
367
 
424
- @Override
425
- public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
426
- super.onCharacteristicRead(gatt, characteristic, status);
368
+ for (Callback readDescriptorCallback: readDescriptorCallbacks) {
369
+ readDescriptorCallback.invoke("Device disconnected");
370
+ }
371
+ readDescriptorCallbacks.clear();
427
372
 
428
- mainHandler.post(() -> {
429
- if (status != BluetoothGatt.GATT_SUCCESS) {
430
- if (status == GATT_AUTH_FAIL || status == GATT_INSUFFICIENT_AUTHENTICATION) {
431
- Log.d(BleManager.LOG_TAG, "Read needs bonding");
373
+ for (Callback connectCallback: connectCallbacks) {
374
+ connectCallback.invoke("Connection error");
375
+ }
376
+ connectCallbacks.clear();
377
+
378
+ writeQueue.clear();
379
+ commandQueue.clear();
380
+ commandQueueBusy = false;
381
+ connected = false;
382
+ clearBuffers();
383
+ commandQueue.clear();
384
+ commandQueueBusy = false;
385
+
386
+ gatt.disconnect();
387
+ gatt.close();
388
+ gatt = null;
389
+ sendConnectionEvent(device, "BleManagerDisconnectPeripheral", BluetoothGatt.GATT_SUCCESS);
390
+
391
+ }
392
+
393
+ });
394
+
395
+ }
396
+
397
+ public void updateRssi(int rssi) {
398
+ advertisingRSSI = rssi;
399
+ }
400
+
401
+ public void updateData(byte[] data) {
402
+ advertisingDataBytes = data;
403
+ }
404
+
405
+ public int unsignedToBytes(byte b) {
406
+ return b & 0xFF;
407
+ }
408
+
409
+ //////
410
+
411
+ @Override
412
+ public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
413
+ super.onCharacteristicChanged(gatt, characteristic);
414
+ try {
415
+ String charString = characteristic.getUuid().toString();
416
+ String service = characteristic.getService().getUuid().toString();
417
+ NotifyBufferContainer buffer = this.bufferedCharacteristics
418
+ .get(this.bufferedCharacteristicsKey(service, charString));
419
+ byte[] dataValue = characteristic.getValue();
420
+ if (buffer != null) {
421
+ buffer.put(dataValue);
422
+ Log.d(BleManager.LOG_TAG, "onCharacteristicChanged-buffering: " +
423
+ buffer.size() + " from peripheral: " + device.getAddress());
424
+
425
+ if (buffer.isBufferFull()) {
426
+ Log.d(BleManager.LOG_TAG, "onCharacteristicChanged sending buffered data " + buffer.size());
427
+
428
+ // send'm and reset
429
+ dataValue = buffer.items.array();
430
+ buffer.resetBuffer();
431
+ } else {
432
+ return;
433
+ }
434
+ }
435
+ Log.d(BleManager.LOG_TAG, "onCharacteristicChanged: " + BleManager.bytesToHex(dataValue)
436
+ + " from peripheral: " + device.getAddress());
437
+ WritableMap map = Arguments.createMap();
438
+ map.putString("peripheral", device.getAddress());
439
+ map.putString("characteristic", charString);
440
+ map.putString("service", service);
441
+ map.putArray("value", BleManager.bytesToWritableArray(dataValue));
442
+ sendEvent("BleManagerDidUpdateValueForCharacteristic", map);
443
+
444
+ } catch (Exception e) {
445
+ Log.d(BleManager.LOG_TAG, "onCharacteristicChanged ERROR: " + e);
446
+ }
447
+ }
448
+
449
+ @Override
450
+ public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
451
+ super.onCharacteristicRead(gatt, characteristic, status);
452
+
453
+ mainHandler.post(() -> {
454
+ if (status != BluetoothGatt.GATT_SUCCESS) {
455
+ if (status == GATT_AUTH_FAIL || status == GATT_INSUFFICIENT_AUTHENTICATION) {
456
+ Log.d(BleManager.LOG_TAG, "Read needs bonding");
457
+ }
458
+
459
+ for (Callback readCallback: readCallbacks) {
460
+ readCallback.invoke(
461
+ "Error reading " + characteristic.getUuid() + " status=" + status,
462
+ null
463
+ );
432
464
  }
433
- readCallback.invoke("Error reading " + characteristic.getUuid() + " status=" + status, null);
434
- readCallback = null;
435
- } else if (readCallback != null) {
465
+ readCallbacks.clear();
466
+ } else if (!readCallbacks.isEmpty()) {
436
467
  final byte[] dataValue = copyOf(characteristic.getValue());
437
- readCallback.invoke(null, BleManager.bytesToWritableArray(dataValue));
438
- readCallback = null;
468
+
469
+ for (Callback readCallback: readCallbacks) {
470
+ readCallback.invoke(null, BleManager.bytesToWritableArray(dataValue));
471
+ }
472
+ readCallbacks.clear();
439
473
  }
440
474
  completedCommand();
441
475
  });
442
476
 
443
- }
477
+ }
444
478
 
445
- @Override
446
- public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
447
- super.onCharacteristicWrite(gatt, characteristic, status);
479
+ @Override
480
+ public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
481
+ super.onCharacteristicWrite(gatt, characteristic, status);
448
482
 
449
483
  mainHandler.post(() -> {
450
484
  if (writeQueue.size() > 0) {
451
485
  byte[] data = writeQueue.get(0);
452
486
  writeQueue.remove(0);
453
- doWrite(characteristic, data, writeCallback);
487
+ doWrite(characteristic, data, null);
454
488
  } else if (status != BluetoothGatt.GATT_SUCCESS) {
455
489
  if (status == GATT_AUTH_FAIL || status == GATT_INSUFFICIENT_AUTHENTICATION) {
456
490
  Log.d(BleManager.LOG_TAG, "Write needs bonding");
457
491
  // *not* doing completedCommand()
458
492
  return;
459
493
  }
460
- writeCallback.invoke( "Error writing " + characteristic.getUuid() + " status=" + status, null);
461
- writeCallback = null;
462
- } else if (writeCallback != null) {
463
- writeCallback.invoke();
464
- writeCallback = null;
494
+ for (Callback writeCallback: writeCallbacks) {
495
+ writeCallback.invoke("Error writing " + characteristic.getUuid() + " status=" + status, null);
496
+ }
497
+ writeCallbacks.clear();
498
+ } else if (!writeCallbacks.isEmpty()) {
499
+ for (Callback writeCallback: writeCallbacks) {
500
+ writeCallback.invoke();
501
+ }
502
+ writeCallbacks.clear();
465
503
  }
466
504
  completedCommand();
467
505
  });
@@ -470,268 +508,355 @@ public class Peripheral extends BluetoothGattCallback {
470
508
  @Override
471
509
  public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
472
510
  mainHandler.post(() -> {
473
- if (registerNotifyCallback != null) {
511
+ if (!registerNotifyCallbacks.isEmpty()) {
474
512
  if (status == BluetoothGatt.GATT_SUCCESS) {
475
- registerNotifyCallback.invoke();
513
+ for (Callback registerNotifyCallback: registerNotifyCallbacks) {
514
+ registerNotifyCallback.invoke();
515
+ }
476
516
  Log.d(BleManager.LOG_TAG, "onDescriptorWrite success");
477
517
  } else {
478
- registerNotifyCallback.invoke("Error writing descriptor status=" + status, null);
518
+ for (Callback registerNotifyCallback: registerNotifyCallbacks) {
519
+ registerNotifyCallback.invoke("Error writing descriptor status=" + status, null);
520
+ }
479
521
  Log.e(BleManager.LOG_TAG, "Error writing descriptor status=" + status);
480
522
  }
481
523
 
482
- registerNotifyCallback = null;
483
- } else {
484
- Log.e(BleManager.LOG_TAG, "onDescriptorWrite with no callback");
485
- }
486
-
487
- completedCommand();
488
- });
489
- }
490
-
491
- @Override
492
- public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
493
- super.onReadRemoteRssi(gatt, rssi, status);
524
+ registerNotifyCallbacks.clear();
525
+ } else {
526
+ Log.e(BleManager.LOG_TAG, "onDescriptorWrite with no callback");
527
+ }
528
+
529
+ completedCommand();
530
+ });
531
+ }
532
+
533
+ @Override
534
+ public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
535
+ super.onDescriptorRead(gatt, descriptor, status);
536
+
537
+ mainHandler.post(() -> {
538
+ if (status != BluetoothGatt.GATT_SUCCESS) {
539
+ if (status == GATT_AUTH_FAIL || status == GATT_INSUFFICIENT_AUTHENTICATION) {
540
+ Log.d(BleManager.LOG_TAG, "Read needs bonding");
541
+ }
542
+
543
+ for (Callback readDescriptorCallback : readDescriptorCallbacks) {
544
+ readDescriptorCallback.invoke(
545
+ "Error reading descriptor " + descriptor.getUuid() + " status=" + status,
546
+ null
547
+ );
548
+ }
549
+ readDescriptorCallbacks.clear();
550
+ } else if (!readDescriptorCallbacks.isEmpty()) {
551
+ final byte[] dataValue = copyOf(descriptor.getValue());
552
+
553
+ for (Callback readDescriptorCallback : readDescriptorCallbacks) {
554
+ readDescriptorCallback.invoke(
555
+ null,
556
+ BleManager.bytesToWritableArray(dataValue)
557
+ );
558
+ }
559
+
560
+ readDescriptorCallbacks.clear();
561
+ }
562
+ completedCommand();
563
+ });
564
+ }
565
+
566
+ @Override
567
+ public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
568
+ super.onReadRemoteRssi(gatt, rssi, status);
494
569
 
495
570
  mainHandler.post(() -> {
496
- if (readRSSICallback != null) {
571
+ if (!readRSSICallbacks.isEmpty()) {
497
572
  if (status == BluetoothGatt.GATT_SUCCESS) {
498
573
  updateRssi(rssi);
499
- readRSSICallback.invoke(null, rssi);
574
+ for (Callback readRSSICallback: readRSSICallbacks) {
575
+ readRSSICallback.invoke(null, rssi);
576
+ }
500
577
  } else {
501
- readRSSICallback.invoke("Error reading RSSI status=" + status, null);
578
+ for (Callback readRSSICallback: readRSSICallbacks) {
579
+ readRSSICallback.invoke("Error reading RSSI status=" + status, null);
580
+ }
502
581
  }
503
582
 
504
- readRSSICallback = null;
505
- }
506
-
507
- completedCommand();
508
- });
509
- }
510
-
511
- private String bufferedCharacteristicsKey(String serviceUUID, String characteristicUUID) {
512
- return serviceUUID + "-" + characteristicUUID;
513
- }
514
-
515
- private void clearBuffers() {
516
- for (Map.Entry<String, NotifyBufferContainer> entry : this.bufferedCharacteristics.entrySet())
517
- entry.getValue().resetBuffer();
518
- }
519
-
520
- private void setNotify(UUID serviceUUID, UUID characteristicUUID, final Boolean notify, Callback callback) {
521
- if (! isConnected() || gatt == null) {
522
- callback.invoke("Device is not connected", null);
523
- completedCommand();
524
- return;
525
- }
526
-
527
- BluetoothGattService service = gatt.getService(serviceUUID);
528
- final BluetoothGattCharacteristic characteristic = findNotifyCharacteristic(service, characteristicUUID);
529
-
530
- if (characteristic == null) {
531
- callback.invoke("Characteristic " + characteristicUUID + " not found");
532
- completedCommand();
533
- return;
534
- }
535
-
536
- if (! gatt.setCharacteristicNotification(characteristic, notify)) {
537
- callback.invoke("Failed to register notification for " + characteristicUUID);
538
- completedCommand();
539
- return;
540
- }
541
-
542
- final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUIDHelper.uuidFromString(CHARACTERISTIC_NOTIFICATION_CONFIG));
543
- if (descriptor == null) {
544
- callback.invoke("Set notification failed for " + characteristicUUID);
545
- completedCommand();
546
- return;
547
- }
548
-
549
- // Prefer notify over indicate
550
- byte[] value;
551
- if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
552
- Log.d(BleManager.LOG_TAG, "Characteristic " + characteristicUUID + " set NOTIFY");
553
- value = notify ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
554
- } else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
555
- Log.d(BleManager.LOG_TAG, "Characteristic " + characteristicUUID + " set INDICATE");
556
- value = notify ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
557
- } else {
558
- String msg = "Characteristic " + characteristicUUID + " does not have NOTIFY or INDICATE property set";
559
- Log.d(BleManager.LOG_TAG, msg);
560
- callback.invoke(msg);
561
- completedCommand();
562
- return;
563
- }
564
- final byte[] finalValue = notify ? value : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
565
- final Callback finalCallback = callback;
566
-
567
- boolean result = false;
568
- try {
569
- result = gatt.setCharacteristicNotification(characteristic, notify);
570
- // Then write to descriptor
571
- descriptor.setValue(finalValue);
572
- registerNotifyCallback = finalCallback;
573
- result &= gatt.writeDescriptor(descriptor);
574
- } catch(Exception e) {
575
- Log.d(BleManager.LOG_TAG, "Exception in setNotify", e);
576
- }
583
+ readRSSICallbacks.clear();
584
+ }
585
+
586
+ completedCommand();
587
+ });
588
+ }
589
+
590
+ private String bufferedCharacteristicsKey(String serviceUUID, String characteristicUUID) {
591
+ return serviceUUID + "-" + characteristicUUID;
592
+ }
593
+
594
+ private void clearBuffers() {
595
+ for (Map.Entry<String, NotifyBufferContainer> entry : this.bufferedCharacteristics.entrySet())
596
+ entry.getValue().resetBuffer();
597
+ }
598
+
599
+ private void setNotify(UUID serviceUUID, UUID characteristicUUID, final Boolean notify, Callback callback) {
600
+ if (!isConnected() || gatt == null) {
601
+ callback.invoke("Device is not connected", null);
602
+ completedCommand();
603
+ return;
604
+ }
605
+
606
+ BluetoothGattService service = gatt.getService(serviceUUID);
607
+ final BluetoothGattCharacteristic characteristic = findNotifyCharacteristic(service, characteristicUUID);
608
+
609
+ if (characteristic == null) {
610
+ callback.invoke("Characteristic " + characteristicUUID + " not found");
611
+ completedCommand();
612
+ return;
613
+ }
614
+
615
+ if (!gatt.setCharacteristicNotification(characteristic, notify)) {
616
+ callback.invoke("Failed to register notification for " + characteristicUUID);
617
+ completedCommand();
618
+ return;
619
+ }
620
+
621
+ final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUIDHelper.uuidFromString(CHARACTERISTIC_NOTIFICATION_CONFIG));
622
+ if (descriptor == null) {
623
+ callback.invoke("Set notification failed for " + characteristicUUID);
624
+ completedCommand();
625
+ return;
626
+ }
627
+
628
+ // Prefer notify over indicate
629
+ byte[] value;
630
+ if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
631
+ Log.d(BleManager.LOG_TAG, "Characteristic " + characteristicUUID + " set NOTIFY");
632
+ value = notify ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
633
+ } else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
634
+ Log.d(BleManager.LOG_TAG, "Characteristic " + characteristicUUID + " set INDICATE");
635
+ value = notify ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
636
+ } else {
637
+ String msg = "Characteristic " + characteristicUUID + " does not have NOTIFY or INDICATE property set";
638
+ Log.d(BleManager.LOG_TAG, msg);
639
+ callback.invoke(msg);
640
+ completedCommand();
641
+ return;
642
+ }
643
+ final byte[] finalValue = notify ? value : BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE;
644
+
645
+ boolean result = false;
646
+ try {
647
+ result = gatt.setCharacteristicNotification(characteristic, notify);
648
+ // Then write to descriptor
649
+ descriptor.setValue(finalValue);
650
+ registerNotifyCallbacks.addLast(callback);
651
+ result &= gatt.writeDescriptor(descriptor);
652
+ } catch (Exception e) {
653
+ Log.d(BleManager.LOG_TAG, "Exception in setNotify", e);
654
+ }
577
655
 
578
656
  if (! result) {
579
- callback.invoke( "writeDescriptor failed for descriptor: " + descriptor.getUuid(), null);
580
- registerNotifyCallback = null;
581
- completedCommand();
582
- }
583
- }
584
-
585
- public void registerNotify(UUID serviceUUID, UUID characteristicUUID, Integer buffer, Callback callback) {
586
- if (!enqueue(() -> {
587
- Log.d(BleManager.LOG_TAG, "registerNotify");
588
- if (buffer > 1) {
589
- Log.d(BleManager.LOG_TAG, "registerNotify using buffer");
590
- String bufferKey = this.bufferedCharacteristicsKey(serviceUUID.toString(), characteristicUUID.toString());
591
- this.bufferedCharacteristics.put(bufferKey, new NotifyBufferContainer(buffer));
592
- }
593
- this.setNotify(serviceUUID, characteristicUUID, true, callback);
594
- })) {
595
- Log.e(BleManager.LOG_TAG, "Could not enqueue setNotify command to register notify");
596
- }
597
- }
598
-
599
- public void removeNotify(UUID serviceUUID, UUID characteristicUUID, Callback callback) {
600
- if (!enqueue(() -> {
601
- Log.d(BleManager.LOG_TAG, "removeNotify");
602
- String bufferKey = this.bufferedCharacteristicsKey(serviceUUID.toString(), characteristicUUID.toString());
603
- if (this.bufferedCharacteristics.containsKey(bufferKey)) {
604
- NotifyBufferContainer buffer = this.bufferedCharacteristics.get(bufferKey);
605
- this.bufferedCharacteristics.remove(bufferKey);
657
+ for (Callback registerNotifyCallback: registerNotifyCallbacks) {
658
+ registerNotifyCallback.invoke("writeDescriptor failed for descriptor: " + descriptor.getUuid(), null);
606
659
  }
607
- this.setNotify(serviceUUID, characteristicUUID, false, callback);
608
- })) {
609
- Log.e(BleManager.LOG_TAG, "Could not enqueue setNotify command to remove notify");
610
- }
611
- }
612
-
613
- // Some devices reuse UUIDs across characteristics, so we can't use
614
- // service.getCharacteristic(characteristicUUID)
615
- // instead check the UUID and properties for each characteristic in the service
616
- // until we find the best match
617
- // This function prefers Notify over Indicate
618
- private BluetoothGattCharacteristic findNotifyCharacteristic(BluetoothGattService service,
619
- UUID characteristicUUID) {
620
-
621
- try {
622
- // Check for Notify first
623
- List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
624
- for (BluetoothGattCharacteristic characteristic : characteristics) {
625
- if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0
626
- && characteristicUUID.equals(characteristic.getUuid())) {
627
- return characteristic;
628
- }
629
- }
630
-
631
- // If there wasn't Notify Characteristic, check for Indicate
632
- for (BluetoothGattCharacteristic characteristic : characteristics) {
633
- if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0
634
- && characteristicUUID.equals(characteristic.getUuid())) {
635
- return characteristic;
636
- }
637
- }
638
-
639
- // As a last resort, try and find ANY characteristic with this UUID, even if it
640
- // doesn't have the correct properties
641
- return service.getCharacteristic(characteristicUUID);
642
- } catch (Exception e) {
643
- Log.e(BleManager.LOG_TAG, "Error retriving characteristic " + characteristicUUID, e);
644
- return null;
660
+ registerNotifyCallbacks.clear();
661
+ completedCommand();
645
662
  }
646
663
  }
647
664
 
648
- public void read(UUID serviceUUID, UUID characteristicUUID, final Callback callback) {
649
- enqueue(() -> {
650
- if (!isConnected() || gatt == null) {
651
- callback.invoke("Device is not connected", null);
652
- completedCommand();
653
- return;
654
- }
655
-
656
- BluetoothGattService service = gatt.getService(serviceUUID);
657
- final BluetoothGattCharacteristic characteristic = findReadableCharacteristic(service, characteristicUUID);
658
-
659
- if (characteristic == null) {
660
- callback.invoke("Characteristic " + characteristicUUID + " not found.", null);
661
- completedCommand();
662
- return;
663
- }
664
-
665
- readCallback = callback;
665
+ public void registerNotify(UUID serviceUUID, UUID characteristicUUID, Integer buffer, Callback callback) {
666
+ if (!enqueue(() -> {
667
+ Log.d(BleManager.LOG_TAG, "registerNotify");
668
+ if (buffer > 1) {
669
+ Log.d(BleManager.LOG_TAG, "registerNotify using buffer");
670
+ String bufferKey = this.bufferedCharacteristicsKey(serviceUUID.toString(), characteristicUUID.toString());
671
+ this.bufferedCharacteristics.put(bufferKey, new NotifyBufferContainer(buffer));
672
+ }
673
+ this.setNotify(serviceUUID, characteristicUUID, true, callback);
674
+ })) {
675
+ Log.e(BleManager.LOG_TAG, "Could not enqueue setNotify command to register notify");
676
+ }
677
+ }
678
+
679
+ public void removeNotify(UUID serviceUUID, UUID characteristicUUID, Callback callback) {
680
+ if (!enqueue(() -> {
681
+ Log.d(BleManager.LOG_TAG, "removeNotify");
682
+ String bufferKey = this.bufferedCharacteristicsKey(serviceUUID.toString(), characteristicUUID.toString());
683
+ if (this.bufferedCharacteristics.containsKey(bufferKey)) {
684
+ NotifyBufferContainer buffer = this.bufferedCharacteristics.get(bufferKey);
685
+ this.bufferedCharacteristics.remove(bufferKey);
686
+ }
687
+ this.setNotify(serviceUUID, characteristicUUID, false, callback);
688
+ })) {
689
+ Log.e(BleManager.LOG_TAG, "Could not enqueue setNotify command to remove notify");
690
+ }
691
+ }
692
+
693
+ // Some devices reuse UUIDs across characteristics, so we can't use
694
+ // service.getCharacteristic(characteristicUUID)
695
+ // instead check the UUID and properties for each characteristic in the service
696
+ // until we find the best match
697
+ // This function prefers Notify over Indicate
698
+ private BluetoothGattCharacteristic findNotifyCharacteristic(BluetoothGattService service,
699
+ UUID characteristicUUID) {
700
+
701
+ try {
702
+ // Check for Notify first
703
+ List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
704
+ for (BluetoothGattCharacteristic characteristic : characteristics) {
705
+ if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0
706
+ && characteristicUUID.equals(characteristic.getUuid())) {
707
+ return characteristic;
708
+ }
709
+ }
710
+
711
+ // If there wasn't Notify Characteristic, check for Indicate
712
+ for (BluetoothGattCharacteristic characteristic : characteristics) {
713
+ if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0
714
+ && characteristicUUID.equals(characteristic.getUuid())) {
715
+ return characteristic;
716
+ }
717
+ }
718
+
719
+ // As a last resort, try and find ANY characteristic with this UUID, even if it
720
+ // doesn't have the correct properties
721
+ return service.getCharacteristic(characteristicUUID);
722
+ } catch (Exception e) {
723
+ Log.e(BleManager.LOG_TAG, "Error retriving characteristic " + characteristicUUID, e);
724
+ return null;
725
+ }
726
+ }
727
+
728
+ public void read(UUID serviceUUID, UUID characteristicUUID, final Callback callback) {
729
+ enqueue(() -> {
730
+ if (!isConnected() || gatt == null) {
731
+ callback.invoke("Device is not connected", null);
732
+ completedCommand();
733
+ return;
734
+ }
735
+
736
+ BluetoothGattService service = gatt.getService(serviceUUID);
737
+ final BluetoothGattCharacteristic characteristic = findReadableCharacteristic(service, characteristicUUID);
738
+
739
+ if (characteristic == null) {
740
+ callback.invoke("Characteristic " + characteristicUUID + " not found.", null);
741
+ completedCommand();
742
+ return;
743
+ }
744
+
745
+ this.readCallbacks.addLast(callback);
666
746
  if (!gatt.readCharacteristic(characteristic)) {
667
- callback.invoke("Read failed", null);
668
- readCallback = null;
747
+ for (Callback readCallback: readCallbacks) {
748
+ readCallback.invoke("Read failed", null);
749
+ }
750
+ readCallbacks.clear();
669
751
  completedCommand();
670
752
  }
671
-
672
753
  });
673
754
  }
674
755
 
675
- private byte[] copyOf(byte[] source) {
676
- if (source == null) return new byte[0];
677
- final int sourceLength = source.length;
678
- final byte[] copy = new byte[sourceLength];
679
- System.arraycopy(source, 0, copy, 0, sourceLength);
680
- return copy;
681
- }
682
-
683
- private boolean enqueue(Runnable command) {
684
- final boolean result = commandQueue.add(command);
685
- if (result) {
686
- nextCommand();
687
- } else {
688
- Log.d(BleManager.LOG_TAG, "could not enqueue command");
689
- }
690
- return result;
691
- }
692
-
693
- private void completedCommand() {
694
- commandQueue.poll();
695
- commandQueueBusy = false;
696
- nextCommand();
697
- }
698
-
699
- private void nextCommand() {
700
- synchronized (this) {
701
- if (commandQueueBusy) {
702
- Log.d(BleManager.LOG_TAG, "Command queue busy");
703
- return;
704
- }
705
-
706
- final Runnable nextCommand = commandQueue.peek();
707
- if (nextCommand == null) {
708
- Log.d(BleManager.LOG_TAG, "Command queue empty");
709
- return;
710
- }
711
-
712
- // Check if we still have a valid gatt object
713
- if (gatt == null) {
714
- Log.d(BleManager.LOG_TAG, "Error, gatt is null");
715
- commandQueue.clear();
716
- commandQueueBusy = false;
717
- return;
718
- }
719
-
720
- // Execute the next command in the queue
721
- commandQueueBusy = true;
722
- mainHandler.post(new Runnable() {
723
- @Override
724
- public void run() {
725
- try {
726
- nextCommand.run();
727
- } catch (Exception ex) {
728
- Log.d(BleManager.LOG_TAG, "Error, command exception");
729
- completedCommand();
730
- }
731
- }
732
- });
733
- }
734
- }
756
+ public void readDescriptor(UUID serviceUUID, UUID characteristicUUID, UUID descriptorUUID, final Callback callback) {
757
+ enqueue(() -> {
758
+ if (!isConnected() || gatt == null) {
759
+ callback.invoke("Device is not connected", null);
760
+ completedCommand();
761
+ return;
762
+ }
763
+
764
+ BluetoothGattService service = gatt.getService(serviceUUID);
765
+ final BluetoothGattCharacteristic characteristic = findReadableCharacteristic(service, characteristicUUID);
766
+
767
+ if (characteristic == null) {
768
+ callback.invoke("Characteristic " + characteristicUUID + " not found.", null);
769
+ completedCommand();
770
+ return;
771
+ }
772
+
773
+ final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descriptorUUID);
774
+ if (descriptor == null) {
775
+ callback.invoke("Read descriptor failed for " + descriptorUUID, null);
776
+ completedCommand();
777
+ return;
778
+ }
779
+
780
+ final int readPermissionBitMask = BluetoothGattDescriptor.PERMISSION_READ
781
+ | BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED
782
+ | BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM;
783
+ if ((descriptor.getPermissions() & readPermissionBitMask) != 0) {
784
+ callback.invoke("Read descriptor failed for " + descriptorUUID + ": Descriptor is missing read permission", null);
785
+ completedCommand();
786
+ return;
787
+ }
788
+
789
+ this.readDescriptorCallbacks.addLast(callback);
790
+ if (!gatt.readDescriptor(descriptor)) {
791
+ for (Callback readDescriptorCallback: readDescriptorCallbacks) {
792
+ readDescriptorCallback.invoke("Reading descriptor failed", null);
793
+ }
794
+ readDescriptorCallbacks.clear();
795
+ completedCommand();
796
+ }
797
+ });
798
+ }
799
+
800
+ private byte[] copyOf(byte[] source) {
801
+ if (source == null) return new byte[0];
802
+ final int sourceLength = source.length;
803
+ final byte[] copy = new byte[sourceLength];
804
+ System.arraycopy(source, 0, copy, 0, sourceLength);
805
+ return copy;
806
+ }
807
+
808
+ private boolean enqueue(Runnable command) {
809
+ final boolean result = commandQueue.add(command);
810
+ if (result) {
811
+ nextCommand();
812
+ } else {
813
+ Log.d(BleManager.LOG_TAG, "could not enqueue command");
814
+ }
815
+ return result;
816
+ }
817
+
818
+ private void completedCommand() {
819
+ commandQueue.poll();
820
+ commandQueueBusy = false;
821
+ nextCommand();
822
+ }
823
+
824
+ private void nextCommand() {
825
+ synchronized (this) {
826
+ if (commandQueueBusy) {
827
+ Log.d(BleManager.LOG_TAG, "Command queue busy");
828
+ return;
829
+ }
830
+
831
+ final Runnable nextCommand = commandQueue.peek();
832
+ if (nextCommand == null) {
833
+ Log.d(BleManager.LOG_TAG, "Command queue empty");
834
+ return;
835
+ }
836
+
837
+ // Check if we still have a valid gatt object
838
+ if (gatt == null) {
839
+ Log.d(BleManager.LOG_TAG, "Error, gatt is null");
840
+ commandQueue.clear();
841
+ commandQueueBusy = false;
842
+ return;
843
+ }
844
+
845
+ // Execute the next command in the queue
846
+ commandQueueBusy = true;
847
+ mainHandler.post(new Runnable() {
848
+ @Override
849
+ public void run() {
850
+ try {
851
+ nextCommand.run();
852
+ } catch (Exception ex) {
853
+ Log.d(BleManager.LOG_TAG, "Error, command exception");
854
+ completedCommand();
855
+ }
856
+ }
857
+ });
858
+ }
859
+ }
735
860
 
736
861
  public void readRSSI(final Callback callback) {
737
862
  if (!enqueue(() -> {
@@ -744,10 +869,12 @@ public class Peripheral extends BluetoothGattCallback {
744
869
  completedCommand();
745
870
  return;
746
871
  } else {
747
- readRSSICallback = callback;
872
+ readRSSICallbacks.addLast(callback);
748
873
  if (!gatt.readRemoteRssi()) {
749
- callback.invoke("Read RSSI failed", null);
750
- readRSSICallback = null;
874
+ for (Callback readRSSICallback: readRSSICallbacks) {
875
+ readRSSICallback.invoke("Read RSSI failed", null);
876
+ }
877
+ readRSSICallbacks.clear();
751
878
  completedCommand();
752
879
  }
753
880
  }
@@ -756,67 +883,67 @@ public class Peripheral extends BluetoothGattCallback {
756
883
  }
757
884
  }
758
885
 
759
- public void refreshCache(Callback callback) {
760
- enqueue(() -> {
761
- try {
762
- Method localMethod = gatt.getClass().getMethod("refresh", new Class[0]);
763
- if (localMethod != null) {
764
- boolean res = ((Boolean) localMethod.invoke(gatt, new Object[0])).booleanValue();
765
- callback.invoke(null, res);
766
- } else {
767
- callback.invoke("Could not refresh cache for device.");
768
- }
769
- } catch (Exception localException) {
770
- Log.e(TAG, "An exception occured while refreshing device");
771
- callback.invoke(localException.getMessage());
772
- } finally {
773
- completedCommand();
774
- }
775
- });
776
- }
777
-
778
- public void retrieveServices(Callback callback) {
779
- enqueue(() -> {
780
- if (!isConnected()) {
781
- callback.invoke("Device is not connected", null);
782
- completedCommand();
783
- return;
784
- } else if (gatt == null) {
785
- callback.invoke("BluetoothGatt is null", null);
786
- completedCommand();
787
- return;
788
- } else {
789
- this.retrieveServicesCallback = callback;
790
- gatt.discoverServices();
791
- }
792
- });
793
- }
794
-
795
- // Some peripherals re-use UUIDs for multiple characteristics so we need to
796
- // check the properties
797
- // and UUID of all characteristics instead of using
798
- // service.getCharacteristic(characteristicUUID)
799
- private BluetoothGattCharacteristic findReadableCharacteristic(BluetoothGattService service,
800
- UUID characteristicUUID) {
801
-
802
- if (service != null) {
803
- int read = BluetoothGattCharacteristic.PROPERTY_READ;
804
-
805
- List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
806
- for (BluetoothGattCharacteristic characteristic : characteristics) {
807
- if ((characteristic.getProperties() & read) != 0
808
- && characteristicUUID.equals(characteristic.getUuid())) {
809
- return characteristic;
810
- }
811
- }
812
-
813
- // As a last resort, try and find ANY characteristic with this UUID, even if it
814
- // doesn't have the correct properties
815
- return service.getCharacteristic(characteristicUUID);
816
- }
817
-
818
- return null;
819
- }
886
+ public void refreshCache(Callback callback) {
887
+ enqueue(() -> {
888
+ try {
889
+ Method localMethod = gatt.getClass().getMethod("refresh", new Class[0]);
890
+ if (localMethod != null) {
891
+ boolean res = ((Boolean) localMethod.invoke(gatt, new Object[0])).booleanValue();
892
+ callback.invoke(null, res);
893
+ } else {
894
+ callback.invoke("Could not refresh cache for device.");
895
+ }
896
+ } catch (Exception localException) {
897
+ Log.e(TAG, "An exception occured while refreshing device");
898
+ callback.invoke(localException.getMessage());
899
+ } finally {
900
+ completedCommand();
901
+ }
902
+ });
903
+ }
904
+
905
+ public void retrieveServices(Callback callback) {
906
+ enqueue(() -> {
907
+ if (!isConnected()) {
908
+ callback.invoke("Device is not connected", null);
909
+ completedCommand();
910
+ return;
911
+ } else if (gatt == null) {
912
+ callback.invoke("BluetoothGatt is null", null);
913
+ completedCommand();
914
+ return;
915
+ } else {
916
+ this.retrieveServicesCallbacks.addLast(callback);
917
+ gatt.discoverServices();
918
+ }
919
+ });
920
+ }
921
+
922
+ // Some peripherals re-use UUIDs for multiple characteristics so we need to
923
+ // check the properties
924
+ // and UUID of all characteristics instead of using
925
+ // service.getCharacteristic(characteristicUUID)
926
+ private BluetoothGattCharacteristic findReadableCharacteristic(BluetoothGattService service,
927
+ UUID characteristicUUID) {
928
+
929
+ if (service != null) {
930
+ int read = BluetoothGattCharacteristic.PROPERTY_READ;
931
+
932
+ List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
933
+ for (BluetoothGattCharacteristic characteristic : characteristics) {
934
+ if ((characteristic.getProperties() & read) != 0
935
+ && characteristicUUID.equals(characteristic.getUuid())) {
936
+ return characteristic;
937
+ }
938
+ }
939
+
940
+ // As a last resort, try and find ANY characteristic with this UUID, even if it
941
+ // doesn't have the correct properties
942
+ return service.getCharacteristic(characteristicUUID);
943
+ }
944
+
945
+ return null;
946
+ }
820
947
 
821
948
  public boolean doWrite(final BluetoothGattCharacteristic characteristic, byte[] data, final Callback callback) {
822
949
  final byte[] copyOfData = copyOf(data);
@@ -824,143 +951,147 @@ public class Peripheral extends BluetoothGattCallback {
824
951
  @Override
825
952
  public void run() {
826
953
  characteristic.setValue(copyOfData);
827
- if (characteristic.getWriteType() == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
828
- writeCallback = callback;
829
- else
830
- writeCallback = null;
954
+ if (
955
+ characteristic.getWriteType() == BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
956
+ && callback != null
957
+ ) {
958
+ writeCallbacks.addLast(callback);
959
+ }
831
960
  if (!gatt.writeCharacteristic(characteristic)) {
832
961
  // write without response, caller will handle the callback
833
- if (writeCallback != null) {
962
+ for (Callback writeCallback: writeCallbacks) {
834
963
  writeCallback.invoke("Write failed", writeCallback);
835
- writeCallback = null;
836
964
  }
965
+ writeCallbacks.clear();
837
966
  completedCommand();
838
967
  }
839
968
  }
840
969
  });
841
970
  }
842
971
 
843
- public void write(UUID serviceUUID, UUID characteristicUUID, byte[] data, Integer maxByteSize, Integer queueSleepTime, Callback callback, int writeType) {
844
- enqueue(() -> {
845
- if (!isConnected() || gatt == null) {
846
- callback.invoke("Device is not connected", null);
847
- completedCommand();
848
- return;
849
- }
850
-
851
- BluetoothGattService service = gatt.getService(serviceUUID);
852
- BluetoothGattCharacteristic characteristic = findWritableCharacteristic(service, characteristicUUID, writeType);
853
-
854
- if (characteristic == null) {
855
- callback.invoke("Characteristic " + characteristicUUID + " not found.");
856
- completedCommand();
857
- return;
858
- }
859
-
860
- characteristic.setWriteType(writeType);
861
-
862
- if (data.length <= maxByteSize) {
863
- if (! doWrite(characteristic, data, callback)) {
864
- callback.invoke("Write failed");
865
- } else {
866
- if (BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE == writeType) {
867
- callback.invoke();
868
- }
869
- }
870
- } else {
871
- int dataLength = data.length;
872
- int count = 0;
873
- byte[] firstMessage = null;
874
- List<byte[]> splittedMessage = new ArrayList<>();
875
-
876
- while (count < dataLength && (dataLength - count > maxByteSize)) {
877
- if (count == 0) {
878
- firstMessage = Arrays.copyOfRange(data, count, count + maxByteSize);
879
- } else {
880
- byte[] splitMessage = Arrays.copyOfRange(data, count, count + maxByteSize);
881
- splittedMessage.add(splitMessage);
882
- }
883
- count += maxByteSize;
884
- }
885
- if (count < dataLength) {
886
- // Other bytes in queue
887
- byte[] splitMessage = Arrays.copyOfRange(data, count, data.length);
888
- splittedMessage.add(splitMessage);
889
- }
890
-
891
- if (BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT == writeType) {
892
- writeQueue.addAll(splittedMessage);
893
- if (! doWrite(characteristic, firstMessage, callback)) {
894
- writeQueue.clear();
895
- callback.invoke("Write failed");
896
- }
897
- } else {
898
- try {
899
- boolean writeError = false;
900
- if (! doWrite(characteristic, firstMessage, callback)) {
901
- writeError = true;
902
- callback.invoke("Write failed");
903
- }
904
- if (! writeError) {
905
- Thread.sleep(queueSleepTime);
906
- for (byte[] message : splittedMessage) {
907
- if (! doWrite(characteristic, message, callback)) {
908
- writeError = true;
909
- callback.invoke("Write failed");
910
- break;
911
- }
912
- Thread.sleep(queueSleepTime);
913
- }
914
- if (! writeError) {
915
- callback.invoke();
916
- }
917
- }
918
- } catch (InterruptedException e) {
919
- callback.invoke("Error during writing");
920
- }
921
- }
922
- }
923
-
924
- completedCommand();
925
- });
926
- }
927
-
928
- public void requestConnectionPriority(int connectionPriority, Callback callback) {
929
- enqueue(() -> {
930
- if (gatt != null) {
931
- if (Build.VERSION.SDK_INT >= LOLLIPOP) {
932
- boolean status = gatt.requestConnectionPriority(connectionPriority);
933
- callback.invoke(null, status);
934
- } else {
935
- callback.invoke("Requesting connection priority requires at least API level 21", null);
936
- }
937
- } else {
938
- callback.invoke("BluetoothGatt is null", null);
939
- }
940
-
941
- completedCommand();
942
- });
943
- }
944
-
945
- public void requestMTU(int mtu, Callback callback) {
946
- enqueue(() -> {
947
- if (!isConnected()) {
948
- callback.invoke("Device is not connected", null);
949
- completedCommand();
950
- return;
951
- }
952
-
953
- if (gatt == null) {
954
- callback.invoke("BluetoothGatt is null", null);
955
- completedCommand();
956
- return;
957
- }
972
+ public void write(UUID serviceUUID, UUID characteristicUUID, byte[] data, Integer maxByteSize, Integer queueSleepTime, Callback callback, int writeType) {
973
+ enqueue(() -> {
974
+ if (!isConnected() || gatt == null) {
975
+ callback.invoke("Device is not connected", null);
976
+ completedCommand();
977
+ return;
978
+ }
979
+
980
+ BluetoothGattService service = gatt.getService(serviceUUID);
981
+ BluetoothGattCharacteristic characteristic = findWritableCharacteristic(service, characteristicUUID, writeType);
982
+
983
+ if (characteristic == null) {
984
+ callback.invoke("Characteristic " + characteristicUUID + " not found.");
985
+ completedCommand();
986
+ return;
987
+ }
988
+
989
+ characteristic.setWriteType(writeType);
990
+
991
+ if (data.length <= maxByteSize) {
992
+ if (!doWrite(characteristic, data, callback)) {
993
+ callback.invoke("Write failed");
994
+ } else {
995
+ if (BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE == writeType) {
996
+ callback.invoke();
997
+ }
998
+ }
999
+ } else {
1000
+ int dataLength = data.length;
1001
+ int count = 0;
1002
+ byte[] firstMessage = null;
1003
+ List<byte[]> splittedMessage = new ArrayList<>();
1004
+
1005
+ while (count < dataLength && (dataLength - count > maxByteSize)) {
1006
+ if (count == 0) {
1007
+ firstMessage = Arrays.copyOfRange(data, count, count + maxByteSize);
1008
+ } else {
1009
+ byte[] splitMessage = Arrays.copyOfRange(data, count, count + maxByteSize);
1010
+ splittedMessage.add(splitMessage);
1011
+ }
1012
+ count += maxByteSize;
1013
+ }
1014
+ if (count < dataLength) {
1015
+ // Other bytes in queue
1016
+ byte[] splitMessage = Arrays.copyOfRange(data, count, data.length);
1017
+ splittedMessage.add(splitMessage);
1018
+ }
1019
+
1020
+ if (BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT == writeType) {
1021
+ writeQueue.addAll(splittedMessage);
1022
+ if (!doWrite(characteristic, firstMessage, callback)) {
1023
+ writeQueue.clear();
1024
+ callback.invoke("Write failed");
1025
+ }
1026
+ } else {
1027
+ try {
1028
+ boolean writeError = false;
1029
+ if (!doWrite(characteristic, firstMessage, callback)) {
1030
+ writeError = true;
1031
+ callback.invoke("Write failed");
1032
+ }
1033
+ if (!writeError) {
1034
+ Thread.sleep(queueSleepTime);
1035
+ for (byte[] message : splittedMessage) {
1036
+ if (!doWrite(characteristic, message, callback)) {
1037
+ writeError = true;
1038
+ callback.invoke("Write failed");
1039
+ break;
1040
+ }
1041
+ Thread.sleep(queueSleepTime);
1042
+ }
1043
+ if (!writeError) {
1044
+ callback.invoke();
1045
+ }
1046
+ }
1047
+ } catch (InterruptedException e) {
1048
+ callback.invoke("Error during writing");
1049
+ }
1050
+ }
1051
+ }
1052
+
1053
+ completedCommand();
1054
+ });
1055
+ }
1056
+
1057
+ public void requestConnectionPriority(int connectionPriority, Callback callback) {
1058
+ enqueue(() -> {
1059
+ if (gatt != null) {
1060
+ if (Build.VERSION.SDK_INT >= LOLLIPOP) {
1061
+ boolean status = gatt.requestConnectionPriority(connectionPriority);
1062
+ callback.invoke(null, status);
1063
+ } else {
1064
+ callback.invoke("Requesting connection priority requires at least API level 21", null);
1065
+ }
1066
+ } else {
1067
+ callback.invoke("BluetoothGatt is null", null);
1068
+ }
1069
+
1070
+ completedCommand();
1071
+ });
1072
+ }
1073
+
1074
+ public void requestMTU(int mtu, Callback callback) {
1075
+ enqueue(() -> {
1076
+ if (!isConnected()) {
1077
+ callback.invoke("Device is not connected", null);
1078
+ completedCommand();
1079
+ return;
1080
+ }
1081
+
1082
+ if (gatt == null) {
1083
+ callback.invoke("BluetoothGatt is null", null);
1084
+ completedCommand();
1085
+ return;
1086
+ }
958
1087
 
959
1088
  if (Build.VERSION.SDK_INT >= LOLLIPOP) {
960
- requestMTUCallback = callback;
1089
+ requestMTUCallbacks.addLast(callback);
961
1090
  if (!gatt.requestMtu(mtu)) {
962
- requestMTUCallback.invoke("Request MTU failed", null);
963
- requestMTUCallback = null;
1091
+ for (Callback requestMTUCallback: requestMTUCallbacks) {
1092
+ requestMTUCallback.invoke("Request MTU failed", null);
1093
+ }
1094
+ requestMTUCallbacks.clear();
964
1095
  completedCommand();
965
1096
  }
966
1097
  } else {
@@ -974,56 +1105,60 @@ public class Peripheral extends BluetoothGattCallback {
974
1105
  public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
975
1106
  super.onMtuChanged(gatt, mtu, status);
976
1107
  mainHandler.post(() -> {
977
- if (requestMTUCallback != null) {
1108
+ if (!requestMTUCallbacks.isEmpty()) {
978
1109
  if (status == BluetoothGatt.GATT_SUCCESS) {
979
- requestMTUCallback.invoke(null, mtu);
1110
+ for (Callback requestMTUCallback: requestMTUCallbacks) {
1111
+ requestMTUCallback.invoke(null, mtu);
1112
+ }
980
1113
  } else {
981
- requestMTUCallback.invoke("Error requesting MTU status = " + status, null);
982
- }
983
-
984
- requestMTUCallback = null;
985
- }
986
-
987
- completedCommand();
988
- });
989
- }
990
-
991
- // Some peripherals re-use UUIDs for multiple characteristics so we need to
992
- // check the properties
993
- // and UUID of all characteristics instead of using
994
- // service.getCharacteristic(characteristicUUID)
995
- private BluetoothGattCharacteristic findWritableCharacteristic(BluetoothGattService service,
996
- UUID characteristicUUID, int writeType) {
997
- try {
998
- // get write property
999
- int writeProperty = BluetoothGattCharacteristic.PROPERTY_WRITE;
1000
- if (writeType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) {
1001
- writeProperty = BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE;
1002
- }
1003
-
1004
- List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
1005
- for (BluetoothGattCharacteristic characteristic : characteristics) {
1006
- if ((characteristic.getProperties() & writeProperty) != 0
1007
- && characteristicUUID.equals(characteristic.getUuid())) {
1008
- return characteristic;
1114
+ for (Callback requestMTUCallback: requestMTUCallbacks) {
1115
+ requestMTUCallback.invoke("Error requesting MTU status = " + status, null);
1116
+ }
1009
1117
  }
1010
- }
1011
1118
 
1012
- // As a last resort, try and find ANY characteristic with this UUID, even if it
1013
- // doesn't have the correct properties
1014
- return service.getCharacteristic(characteristicUUID);
1015
- } catch (Exception e) {
1016
- Log.e(BleManager.LOG_TAG, "Error on findWritableCharacteristic", e);
1017
- return null;
1018
- }
1019
- }
1020
-
1021
- private String generateHashKey(BluetoothGattCharacteristic characteristic) {
1022
- return generateHashKey(characteristic.getService().getUuid(), characteristic);
1023
- }
1024
-
1025
- private String generateHashKey(UUID serviceUUID, BluetoothGattCharacteristic characteristic) {
1026
- return String.valueOf(serviceUUID) + "|" + characteristic.getUuid() + "|" + characteristic.getInstanceId();
1027
- }
1119
+ requestMTUCallbacks.clear();
1120
+ }
1121
+
1122
+ completedCommand();
1123
+ });
1124
+ }
1125
+
1126
+ // Some peripherals re-use UUIDs for multiple characteristics so we need to
1127
+ // check the properties
1128
+ // and UUID of all characteristics instead of using
1129
+ // service.getCharacteristic(characteristicUUID)
1130
+ private BluetoothGattCharacteristic findWritableCharacteristic(BluetoothGattService service,
1131
+ UUID characteristicUUID, int writeType) {
1132
+ try {
1133
+ // get write property
1134
+ int writeProperty = BluetoothGattCharacteristic.PROPERTY_WRITE;
1135
+ if (writeType == BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE) {
1136
+ writeProperty = BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE;
1137
+ }
1138
+
1139
+ List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
1140
+ for (BluetoothGattCharacteristic characteristic : characteristics) {
1141
+ if ((characteristic.getProperties() & writeProperty) != 0
1142
+ && characteristicUUID.equals(characteristic.getUuid())) {
1143
+ return characteristic;
1144
+ }
1145
+ }
1146
+
1147
+ // As a last resort, try and find ANY characteristic with this UUID, even if it
1148
+ // doesn't have the correct properties
1149
+ return service.getCharacteristic(characteristicUUID);
1150
+ } catch (Exception e) {
1151
+ Log.e(BleManager.LOG_TAG, "Error on findWritableCharacteristic", e);
1152
+ return null;
1153
+ }
1154
+ }
1155
+
1156
+ private String generateHashKey(BluetoothGattCharacteristic characteristic) {
1157
+ return generateHashKey(characteristic.getService().getUuid(), characteristic);
1158
+ }
1159
+
1160
+ private String generateHashKey(UUID serviceUUID, BluetoothGattCharacteristic characteristic) {
1161
+ return serviceUUID + "|" + characteristic.getUuid() + "|" + characteristic.getInstanceId();
1162
+ }
1028
1163
 
1029
1164
  }