simplernble 1.2.1-dev126 → 1.2.1-dev129

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.
@@ -92,6 +92,10 @@ std::shared_ptr<Promise<bool>> HybridSimpleRnBLE::bluetoothEnabled() {
92
92
  return run<bool>(lifecycle(), [] { return SimpleBLE::Adapter::bluetooth_enabled(); });
93
93
  }
94
94
 
95
+ bool HybridSimpleRnBLE::getNegotiateMtu() { return SimpleBLE::Config::Android::negotiate_mtu; }
96
+
97
+ void HybridSimpleRnBLE::setNegotiateMtu(bool enabled) { SimpleBLE::Config::Android::negotiate_mtu = enabled; }
98
+
95
99
  ConnectionPriorityRequest HybridSimpleRnBLE::getConnectionPriorityRequest() {
96
100
  return convert_back(PRIORITIES, SimpleBLE::Config::Android::connection_priority_request);
97
101
  }
@@ -16,6 +16,8 @@ class HybridSimpleRnBLE : public HybridSimpleRnBLESpec {
16
16
  std::shared_ptr<Promise<std::vector<std::shared_ptr<HybridAdapterHandleSpec>>>> getAdapters() override;
17
17
  std::shared_ptr<Promise<bool>> bluetoothEnabled() override;
18
18
 
19
+ bool getNegotiateMtu() override;
20
+ void setNegotiateMtu(bool enabled) override;
19
21
  ConnectionPriorityRequest getConnectionPriorityRequest() override;
20
22
  void setConnectionPriorityRequest(ConnectionPriorityRequest request) override;
21
23
  void resetConfig() override;
package/lib/config.d.ts CHANGED
@@ -3,6 +3,8 @@ export type ConnectionPriorityRequest = 'disabled' | 'balanced' | 'high' | 'lowP
3
3
  export interface AndroidConfig {
4
4
  /** Requested after each connection; 'disabled' leaves Android's default. */
5
5
  connectionPriorityRequest: ConnectionPriorityRequest;
6
+ /** Negotiate MTU on connect. Defaults to true. */
7
+ negotiateMtu: boolean;
6
8
  }
7
9
  /**
8
10
  * App-wide settings of each backend. Set them before the first call that
package/lib/config.js CHANGED
@@ -17,8 +17,17 @@ exports.Config = Object.freeze({
17
17
  throw new TypeError(`connectionPriorityRequest must be one of ${PRIORITIES.join(', ')}`);
18
18
  native_1.native.connectionPriorityRequest = value;
19
19
  },
20
+ get negotiateMtu() {
21
+ return native_1.native.negotiateMtu;
22
+ },
23
+ set negotiateMtu(value) {
24
+ if (typeof value !== 'boolean')
25
+ throw new TypeError('negotiateMtu must be a boolean');
26
+ native_1.native.negotiateMtu = value;
27
+ },
20
28
  reset() {
21
29
  native_1.native.connectionPriorityRequest = 'disabled';
30
+ native_1.native.negotiateMtu = true;
22
31
  },
23
32
  }),
24
33
  resetAll() {
@@ -114,6 +114,7 @@ export interface SimpleRnBLE extends Native {
114
114
  getAdapters(): Promise<AdapterHandle[]>;
115
115
  bluetoothEnabled(): Promise<boolean>;
116
116
  connectionPriorityRequest: ConnectionPriorityRequest;
117
+ negotiateMtu: boolean;
117
118
  resetConfig(): void;
118
119
  logLevel: number;
119
120
  setLogCallback(callback: ((entry: LogEntry) => void) | undefined): void;
package/native/VERSION CHANGED
@@ -1 +1 @@
1
- 1.2.1-dev126
1
+ 1.2.1-dev129
@@ -47,8 +47,12 @@ namespace Android {
47
47
  enum class ConnectionPriorityRequest { DISABLED = -1, BALANCED = 0, HIGH = 1, LOW_POWER = 2, DCK = 3 };
48
48
 
49
49
  extern SIMPLEBLE_EXPORT ConnectionPriorityRequest connection_priority_request;
50
+ extern SIMPLEBLE_EXPORT bool negotiate_mtu;
50
51
 
51
- static void reset() { connection_priority_request = ConnectionPriorityRequest::DISABLED; }
52
+ static void reset() {
53
+ connection_priority_request = ConnectionPriorityRequest::DISABLED;
54
+ negotiate_mtu = true;
55
+ }
52
56
  } // namespace Android
53
57
 
54
58
  namespace Dongl {
@@ -16,6 +16,7 @@ namespace Config {
16
16
 
17
17
  namespace Android {
18
18
  ConnectionPriorityRequest connection_priority_request = ConnectionPriorityRequest::DISABLED;
19
+ bool negotiate_mtu = true;
19
20
  } // namespace Android
20
21
 
21
22
  namespace Dongl {
@@ -26,6 +26,7 @@ PeripheralAndroid::PeripheralAndroid(Android::BluetoothDevice device) : _device(
26
26
  // If a connection has been established, request service discovery.
27
27
  _gatt.discoverServices();
28
28
  } else {
29
+ _connection_pending = false;
29
30
  // If a connection has been lost, close the GATT object.
30
31
  _gatt.close();
31
32
  SAFE_CALLBACK_CALL(callback_on_disconnected_);
@@ -34,10 +35,33 @@ PeripheralAndroid::PeripheralAndroid(Android::BluetoothDevice device) : _device(
34
35
  });
35
36
 
36
37
  _btGattCallback.set_callback_onServicesDiscovered([this]() {
37
- // Once services have been discovered, store them and notify the user.
38
38
  _services = _gatt.getServices();
39
- SAFE_CALLBACK_CALL(callback_on_connected_);
40
- _connection_cv.notify_all();
39
+
40
+ // Android can report a retained ACL connection's MTU during discovery.
41
+ // Request it only when needed, after discovery and before completing connect.
42
+ if (Config::Android::negotiate_mtu && !_mtu_observed) {
43
+ if (_gatt.requestMtu(517)) {
44
+ return;
45
+ }
46
+
47
+ SIMPLEBLE_LOG_WARN("Failed to start MTU negotiation; using the last known ATT MTU");
48
+ }
49
+
50
+ _complete_connection();
51
+ });
52
+
53
+ _btGattCallback.set_callback_onMtuChanged([this](int status) {
54
+ if (status == 0) {
55
+ _mtu_observed = true;
56
+ }
57
+
58
+ if (_btGattCallback.services_discovered) {
59
+ if (status != 0 && _connection_pending) {
60
+ SIMPLEBLE_LOG_WARN("MTU negotiation failed; using the last known ATT MTU");
61
+ }
62
+
63
+ _complete_connection();
64
+ }
41
65
  });
42
66
  }
43
67
 
@@ -84,10 +108,18 @@ int16_t PeripheralAndroid::rssi() { return rssi_; }
84
108
 
85
109
  int16_t PeripheralAndroid::tx_power() { return tx_power_; }
86
110
 
87
- uint16_t PeripheralAndroid::mtu() { return _btGattCallback.mtu; }
111
+ uint16_t PeripheralAndroid::mtu() {
112
+ if (!is_connected()) {
113
+ return 0;
114
+ }
115
+
116
+ return _btGattCallback.mtu.load() - 3;
117
+ }
88
118
 
89
119
  void PeripheralAndroid::connect() {
90
120
  _btGattCallback.mtu = 23;
121
+ _connection_pending = true;
122
+ _mtu_observed = false;
91
123
  _gatt = _device.connectGatt(false, _btGattCallback);
92
124
 
93
125
  // Wait for the connection to be confirmed.
@@ -95,7 +127,7 @@ void PeripheralAndroid::connect() {
95
127
  std::unique_lock<std::mutex> lock(_connection_mutex);
96
128
  bool connected = _connection_cv.wait_for(lock, 8s, [this]() { return is_connected(); });
97
129
  if (!connected) {
98
- throw SimpleBLE::Exception::OperationFailed("Failed to connect to device");
130
+ _handle_connection_timeout();
99
131
  }
100
132
  }
101
133
 
@@ -110,7 +142,9 @@ void PeripheralAndroid::disconnect() {
110
142
  }
111
143
  }
112
144
 
113
- bool PeripheralAndroid::is_connected() { return _btGattCallback.connected && _btGattCallback.services_discovered; }
145
+ bool PeripheralAndroid::is_connected() {
146
+ return _btGattCallback.connected && _btGattCallback.services_discovered && !_connection_pending;
147
+ }
114
148
 
115
149
  bool PeripheralAndroid::is_connectable() { return connectable_; }
116
150
 
@@ -334,6 +368,29 @@ void PeripheralAndroid::set_callback_on_disconnected(std::function<void()> on_di
334
368
  }
335
369
  }
336
370
 
371
+ void PeripheralAndroid::_complete_connection() {
372
+ bool connection_pending = _connection_pending.exchange(false);
373
+ if (!connection_pending) {
374
+ return;
375
+ }
376
+
377
+ SAFE_CALLBACK_CALL(callback_on_connected_);
378
+ _connection_cv.notify_all();
379
+ }
380
+
381
+ void PeripheralAndroid::_handle_connection_timeout() {
382
+ bool waiting_for_mtu = _btGattCallback.services_discovered && _connection_pending;
383
+ _btGattCallback.connected = false;
384
+ _btGattCallback.services_discovered = false;
385
+ _connection_pending = false;
386
+ _gatt.disconnect();
387
+ _gatt.close();
388
+ if (waiting_for_mtu) {
389
+ throw SimpleBLE::Exception::OperationFailed("Timed out waiting for MTU negotiation");
390
+ }
391
+ throw SimpleBLE::Exception::OperationFailed("Failed to connect to device");
392
+ }
393
+
337
394
  // NOTE: This approach to retrieve the characteristic and descriptor objects is not ideal, as it involves
338
395
  // iterating on Java objects and returning a copy of the desired object. This will be improved in the future
339
396
  // if performance becomes a bottleneck.
@@ -10,6 +10,7 @@
10
10
  #include <types/android/bluetooth/le/ScanResult.h>
11
11
  #include <bridge/BluetoothGattCallback.h>
12
12
 
13
+ #include <atomic>
13
14
  #include <condition_variable>
14
15
  #include <map>
15
16
  #include <mutex>
@@ -77,10 +78,15 @@ class PeripheralAndroid : public PeripheralBase {
77
78
  std::mutex _disconnection_mutex;
78
79
  std::condition_variable _connection_cv;
79
80
  std::condition_variable _disconnection_cv;
81
+ std::atomic<bool> _connection_pending = false;
82
+ std::atomic<bool> _mtu_observed = false;
80
83
 
81
84
  kvn::safe_callback<void()> callback_on_connected_;
82
85
  kvn::safe_callback<void()> callback_on_disconnected_;
83
86
 
87
+ void _complete_connection();
88
+ void _handle_connection_timeout();
89
+
84
90
  Android::BluetoothGattCharacteristic _fetch_characteristic(const BluetoothUUID& service_uuid,
85
91
  const BluetoothUUID& characteristic_uuid);
86
92
  Android::BluetoothGattDescriptor _fetch_descriptor(const BluetoothUUID& service_uuid,
@@ -69,6 +69,14 @@ void BluetoothGattCallback::set_callback_onServicesDiscovered(std::function<void
69
69
  }
70
70
  }
71
71
 
72
+ void BluetoothGattCallback::set_callback_onMtuChanged(std::function<void(int)> callback) {
73
+ if (callback) {
74
+ _callback_onMtuChanged.load(callback);
75
+ } else {
76
+ _callback_onMtuChanged.unload();
77
+ }
78
+ }
79
+
72
80
  void BluetoothGattCallback::set_callback_onCharacteristicChanged(SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> characteristic,
73
81
  std::function<void(std::vector<uint8_t>)> callback) {
74
82
  if (callback) {
@@ -229,7 +237,13 @@ void BluetoothGattCallback::jni_onConnectionStateChangeCallback(SimpleJNI::Objec
229
237
 
230
238
  BluetoothGattCallback* obj = GET_CALLBACK_OBJECT_OR_RETURN(thiz_obj);
231
239
  const bool connected = new_state == 2;
232
- obj->connected = connected;
240
+ bool was_connected = obj->connected.exchange(connected);
241
+
242
+ // Some Android stacks repeat the connected event. Start connection setup only once.
243
+ if (connected && was_connected) {
244
+ return;
245
+ }
246
+
233
247
  if (!connected) {
234
248
  // Services need to be rediscovered on the next connection.
235
249
  obj->services_discovered = false;
@@ -313,9 +327,13 @@ void BluetoothGattCallback::jni_onMtuChangedCallback(SimpleJNI::Object<SimpleJNI
313
327
  BluetoothGattCallback* obj = GET_CALLBACK_OBJECT_OR_RETURN(thiz_obj);
314
328
  if (status != 0) {
315
329
  SIMPLEBLE_LOG_ERROR(fmt::format("MTU change failed with GATT status {}", status));
316
- return;
330
+ } else if (mtu < 23 || mtu > 517) {
331
+ SIMPLEBLE_LOG_ERROR(fmt::format("Invalid ATT MTU {}", mtu));
332
+ status = -1;
333
+ } else {
334
+ obj->mtu = mtu;
317
335
  }
318
- obj->mtu = mtu;
336
+ SAFE_CALLBACK_CALL(obj->_callback_onMtuChanged, status);
319
337
  }
320
338
 
321
339
  void BluetoothGattCallback::jni_onPhyReadCallback(SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> thiz_obj, jint tx_phy, jint rx_phy, jint status) {
@@ -21,6 +21,7 @@ class BluetoothGattCallback {
21
21
 
22
22
  void set_callback_onConnectionStateChange(std::function<void(bool)> callback);
23
23
  void set_callback_onServicesDiscovered(std::function<void(void)> callback);
24
+ void set_callback_onMtuChanged(std::function<void(int)> callback);
24
25
 
25
26
  void set_callback_onCharacteristicChanged(SimpleJNI::Object<SimpleJNI::GlobalRef, jobject> characteristic,
26
27
  std::function<void(std::vector<uint8_t> value)> callback);
@@ -84,6 +85,7 @@ class BluetoothGattCallback {
84
85
 
85
86
  kvn::safe_callback<void(bool)> _callback_onConnectionStateChange;
86
87
  kvn::safe_callback<void()> _callback_onServicesDiscovered;
88
+ kvn::safe_callback<void(int)> _callback_onMtuChanged;
87
89
 
88
90
  kvn::safe_map<SimpleJNI::Object<SimpleJNI::GlobalRef, jobject>, kvn::safe_callback<void(std::vector<uint8_t>)>, SimpleJNI::ObjectComparator<SimpleJNI::GlobalRef, jobject>>
89
91
  _callback_onCharacteristicChanged;
@@ -100,4 +102,4 @@ class BluetoothGattCallback {
100
102
 
101
103
  } // namespace Bridge
102
104
  } // namespace Android
103
- } // namespace SimpleBLE
105
+ } // namespace SimpleBLE
@@ -19,6 +19,7 @@ jmethodID BluetoothGatt::_method_setCharacteristicNotification = nullptr;
19
19
  jmethodID BluetoothGatt::_method_writeCharacteristic = nullptr;
20
20
  jmethodID BluetoothGatt::_method_writeDescriptor = nullptr;
21
21
  jmethodID BluetoothGatt::_method_requestConnectionPriority = nullptr;
22
+ jmethodID BluetoothGatt::_method_requestMtu = nullptr;
22
23
  // Define the JNI descriptor
23
24
  const SimpleJNI::JNIDescriptor BluetoothGatt::descriptor{
24
25
  "android/bluetooth/BluetoothGatt", // Java class name
@@ -34,7 +35,8 @@ const SimpleJNI::JNIDescriptor BluetoothGatt::descriptor{
34
35
  {"setCharacteristicNotification", "(Landroid/bluetooth/BluetoothGattCharacteristic;Z)Z", &_method_setCharacteristicNotification},
35
36
  {"writeCharacteristic", "(Landroid/bluetooth/BluetoothGattCharacteristic;)Z", &_method_writeCharacteristic},
36
37
  {"writeDescriptor", "(Landroid/bluetooth/BluetoothGattDescriptor;)Z", &_method_writeDescriptor},
37
- {"requestConnectionPriority", "(I)Z", &_method_requestConnectionPriority}
38
+ {"requestConnectionPriority", "(I)Z", &_method_requestConnectionPriority},
39
+ {"requestMtu", "(I)Z", &_method_requestMtu}
38
40
  }};
39
41
 
40
42
  const SimpleJNI::AutoRegister<BluetoothGatt> BluetoothGatt::registrar{&descriptor};
@@ -111,5 +113,10 @@ bool BluetoothGatt::requestConnectionPriority(int connectionPriority) {
111
113
  return _obj.call_boolean_method(_method_requestConnectionPriority, connectionPriority);
112
114
  }
113
115
 
116
+ bool BluetoothGatt::requestMtu(int mtu) {
117
+ if (!_obj) throw std::runtime_error("BluetoothGatt is not initialized");
118
+ return _obj.call_boolean_method(_method_requestMtu, mtu);
119
+ }
120
+
114
121
  } // namespace Android
115
122
  } // namespace SimpleBLE
@@ -42,7 +42,7 @@ class BluetoothGatt {
42
42
  // void readPhy();
43
43
  // bool readRemoteRssi();
44
44
  bool requestConnectionPriority(int connectionPriority);
45
- // bool requestMtu(int mtu);
45
+ bool requestMtu(int mtu);
46
46
  bool setCharacteristicNotification(BluetoothGattCharacteristic characteristic, bool enable);
47
47
  // void setPreferredPhy(int txPhy, int rxPhy, int phyOptions);
48
48
  bool writeCharacteristic(BluetoothGattCharacteristic characteristic);
@@ -66,6 +66,7 @@ class BluetoothGatt {
66
66
  static jmethodID _method_writeCharacteristic;
67
67
  static jmethodID _method_writeDescriptor;
68
68
  static jmethodID _method_requestConnectionPriority;
69
+ static jmethodID _method_requestMtu;
69
70
  // JNI descriptor for auto-registration
70
71
  static const SimpleJNI::JNIDescriptor descriptor;
71
72
  static const SimpleJNI::AutoRegister<BluetoothGatt> registrar;
@@ -16,6 +16,8 @@ namespace margelo::nitro::simplernble {
16
16
  registerHybrids(this, [](Prototype& prototype) {
17
17
  prototype.registerHybridGetter("connectionPriorityRequest", &HybridSimpleRnBLESpec::getConnectionPriorityRequest);
18
18
  prototype.registerHybridSetter("connectionPriorityRequest", &HybridSimpleRnBLESpec::setConnectionPriorityRequest);
19
+ prototype.registerHybridGetter("negotiateMtu", &HybridSimpleRnBLESpec::getNegotiateMtu);
20
+ prototype.registerHybridSetter("negotiateMtu", &HybridSimpleRnBLESpec::setNegotiateMtu);
19
21
  prototype.registerHybridGetter("logLevel", &HybridSimpleRnBLESpec::getLogLevel);
20
22
  prototype.registerHybridSetter("logLevel", &HybridSimpleRnBLESpec::setLogLevel);
21
23
  prototype.registerHybridMethod("getBackends", &HybridSimpleRnBLESpec::getBackends);
@@ -62,6 +62,8 @@ namespace margelo::nitro::simplernble {
62
62
  // Properties
63
63
  virtual ConnectionPriorityRequest getConnectionPriorityRequest() = 0;
64
64
  virtual void setConnectionPriorityRequest(ConnectionPriorityRequest connectionPriorityRequest) = 0;
65
+ virtual bool getNegotiateMtu() = 0;
66
+ virtual void setNegotiateMtu(bool negotiateMtu) = 0;
65
67
  virtual double getLogLevel() = 0;
66
68
  virtual void setLogLevel(double logLevel) = 0;
67
69
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simplernble",
3
- "version": "1.2.1-dev126",
3
+ "version": "1.2.1-dev129",
4
4
  "description": "SimpleBLE for React Native: a cross-platform Bluetooth Low Energy library for iOS and Android",
5
5
  "keywords": [
6
6
  "react-native",
@@ -47,8 +47,12 @@ namespace Android {
47
47
  enum class ConnectionPriorityRequest { DISABLED = -1, BALANCED = 0, HIGH = 1, LOW_POWER = 2, DCK = 3 };
48
48
 
49
49
  extern SIMPLEBLE_EXPORT ConnectionPriorityRequest connection_priority_request;
50
+ extern SIMPLEBLE_EXPORT bool negotiate_mtu;
50
51
 
51
- static void reset() { connection_priority_request = ConnectionPriorityRequest::DISABLED; }
52
+ static void reset() {
53
+ connection_priority_request = ConnectionPriorityRequest::DISABLED;
54
+ negotiate_mtu = true;
55
+ }
52
56
  } // namespace Android
53
57
 
54
58
  namespace Dongl {
package/src/config.ts CHANGED
@@ -8,6 +8,8 @@ const PRIORITIES: readonly ConnectionPriorityRequest[] = ['disabled', 'balanced'
8
8
  export interface AndroidConfig {
9
9
  /** Requested after each connection; 'disabled' leaves Android's default. */
10
10
  connectionPriorityRequest: ConnectionPriorityRequest;
11
+ /** Negotiate MTU on connect. Defaults to true. */
12
+ negotiateMtu: boolean;
11
13
  }
12
14
 
13
15
  /**
@@ -26,8 +28,16 @@ export const Config: {
26
28
  if (!PRIORITIES.includes(value)) throw new TypeError(`connectionPriorityRequest must be one of ${PRIORITIES.join(', ')}`);
27
29
  native.connectionPriorityRequest = value;
28
30
  },
31
+ get negotiateMtu(): boolean {
32
+ return native.negotiateMtu;
33
+ },
34
+ set negotiateMtu(value: boolean) {
35
+ if (typeof value !== 'boolean') throw new TypeError('negotiateMtu must be a boolean');
36
+ native.negotiateMtu = value;
37
+ },
29
38
  reset(): void {
30
39
  native.connectionPriorityRequest = 'disabled';
40
+ native.negotiateMtu = true;
31
41
  },
32
42
  }),
33
43
  resetAll(): void {
@@ -142,6 +142,7 @@ export interface SimpleRnBLE extends Native {
142
142
  bluetoothEnabled(): Promise<boolean>;
143
143
 
144
144
  connectionPriorityRequest: ConnectionPriorityRequest;
145
+ negotiateMtu: boolean;
145
146
  resetConfig(): void;
146
147
 
147
148
  logLevel: number;