react-native-ble-nitro 1.13.0 → 1.15.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.
- package/README.md +53 -1
- package/android/build.gradle +6 -0
- package/android/src/main/java/com/margelo/nitro/co/zyke/ble/BleNitroBleManager.kt +128 -11
- package/android/src/main/java/com/margelo/nitro/co/zyke/ble/CccdSubscription.kt +38 -0
- package/android/src/test/java/com/margelo/nitro/co/zyke/ble/CccdSubscriptionTest.kt +48 -0
- package/ios/BleNitroBleManager.swift +28 -3
- package/lib/commonjs/index.d.ts +1 -1
- package/lib/commonjs/index.d.ts.map +1 -1
- package/lib/commonjs/index.js +2 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/manager.d.ts +24 -1
- package/lib/commonjs/manager.d.ts.map +1 -1
- package/lib/commonjs/manager.js +33 -1
- package/lib/commonjs/manager.js.map +1 -1
- package/lib/commonjs/specs/NativeBleNitro.nitro.d.ts +14 -0
- package/lib/commonjs/specs/NativeBleNitro.nitro.d.ts.map +1 -1
- package/lib/commonjs/specs/NativeBleNitro.nitro.js +7 -1
- package/lib/commonjs/specs/NativeBleNitro.nitro.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/manager.d.ts +24 -1
- package/lib/manager.js +32 -1
- package/lib/specs/NativeBleNitro.nitro.d.ts +14 -0
- package/lib/specs/NativeBleNitro.nitro.js +6 -0
- package/nitrogen/generated/android/c++/JAndroidConnectionPriority.hpp +61 -0
- package/nitrogen/generated/android/c++/JBLEDevice.hpp +9 -1
- package/nitrogen/generated/android/c++/JFunc_void_std__optional_std__variant_nitro__NullType__BLEDevice___std__optional_std__variant_nitro__NullType__std__string__.hpp +4 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__vector_BLEDevice_.hpp +4 -0
- package/nitrogen/generated/android/c++/JHybridNativeBleNitroFactorySpec.cpp +8 -0
- package/nitrogen/generated/android/c++/JHybridNativeBleNitroSpec.cpp +17 -0
- package/nitrogen/generated/android/c++/JHybridNativeBleNitroSpec.hpp +1 -0
- package/nitrogen/generated/android/c++/JServiceData.hpp +80 -0
- package/nitrogen/generated/android/c++/JServiceDataEntry.hpp +63 -0
- package/nitrogen/generated/android/c++/JVariant_NullType_BLEDevice.hpp +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/AndroidConnectionPriority.kt +24 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/BLEDevice.kt +5 -2
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/HybridNativeBleNitroSpec.kt +4 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/ServiceData.kt +38 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/co/zyke/ble/ServiceDataEntry.kt +41 -0
- package/nitrogen/generated/ios/BleNitro-Swift-Cxx-Bridge.hpp +17 -0
- package/nitrogen/generated/ios/BleNitro-Swift-Cxx-Umbrella.hpp +9 -0
- package/nitrogen/generated/ios/c++/HybridNativeBleNitroFactorySpecSwift.hpp +6 -0
- package/nitrogen/generated/ios/c++/HybridNativeBleNitroSpecSwift.hpp +17 -0
- package/nitrogen/generated/ios/swift/AndroidConnectionPriority.swift +44 -0
- package/nitrogen/generated/ios/swift/BLEDevice.swift +7 -2
- package/nitrogen/generated/ios/swift/HybridNativeBleNitroSpec.swift +1 -0
- package/nitrogen/generated/ios/swift/HybridNativeBleNitroSpec_cxx.swift +12 -0
- package/nitrogen/generated/ios/swift/ServiceData.swift +35 -0
- package/nitrogen/generated/ios/swift/ServiceDataEntry.swift +34 -0
- package/nitrogen/generated/shared/c++/AndroidConnectionPriority.hpp +63 -0
- package/nitrogen/generated/shared/c++/BLEDevice.hpp +8 -1
- package/nitrogen/generated/shared/c++/HybridNativeBleNitroSpec.cpp +1 -0
- package/nitrogen/generated/shared/c++/HybridNativeBleNitroSpec.hpp +4 -0
- package/nitrogen/generated/shared/c++/ServiceData.hpp +85 -0
- package/nitrogen/generated/shared/c++/ServiceDataEntry.hpp +88 -0
- package/package.json +1 -1
- package/src/__tests__/index.test.ts +69 -2
- package/src/index.ts +3 -0
- package/src/manager.ts +50 -0
- package/src/specs/NativeBleNitro.nitro.ts +18 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ Originally developed for [Zyke Band](https://zykeband.com?utm_source=github&utm_
|
|
|
13
13
|
- 📱 **iOS Support**: Complete iOS implementation with Swift and Core Bluetooth
|
|
14
14
|
- 🤖 **Android Support**: Complete Android implementation with Kotlin and Android BLE APIs
|
|
15
15
|
- 🎯 **Type-Safe**: Full TypeScript support with comprehensive type definitions
|
|
16
|
+
- 📡 **Advertisement Data**: Extract advertised service data and manufacturer data from scan results
|
|
16
17
|
- 🔧 **Expo Ready**: Built-in Expo config plugin for easy setup
|
|
17
18
|
- 🏗️ **New Architecture**: Full support for React Native's new architecture
|
|
18
19
|
- ⚡ **Zero Bridge**: Direct JSI communication eliminates bridge bottlenecks
|
|
@@ -69,7 +70,7 @@ npx pod-install # iOS only
|
|
|
69
70
|
### Basic Setup
|
|
70
71
|
|
|
71
72
|
```typescript
|
|
72
|
-
import { BleNitro, BleNitroManager, BLEState, AndroidScanMode, type BLEDevice } from 'react-native-ble-nitro';
|
|
73
|
+
import { BleNitro, BleNitroManager, BLEState, AndroidScanMode, AndroidConnectionPriority, type BLEDevice } from 'react-native-ble-nitro';
|
|
73
74
|
|
|
74
75
|
// Get the singleton instance
|
|
75
76
|
const ble = BleNitro.instance();
|
|
@@ -127,6 +128,16 @@ ble.startScan({
|
|
|
127
128
|
androidScanMode: AndroidScanMode.Balanced // Optional: Android scan mode
|
|
128
129
|
}, (device) => {
|
|
129
130
|
console.log('Discovered device:', device);
|
|
131
|
+
|
|
132
|
+
// Advertised service data, keyed by service UUID (ArrayBuffer payloads)
|
|
133
|
+
device.serviceData.services.forEach(({ uuid, data }) => {
|
|
134
|
+
console.log(`Service data for ${uuid}:`, new Uint8Array(data));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Advertised manufacturer data, keyed by company identifier
|
|
138
|
+
device.manufacturerData.companyIdentifiers.forEach(({ id, data }) => {
|
|
139
|
+
console.log(`Manufacturer data for ${id}:`, new Uint8Array(data));
|
|
140
|
+
});
|
|
130
141
|
}, (error) => {
|
|
131
142
|
// only called on Android
|
|
132
143
|
console.error('Scan error:', error);
|
|
@@ -189,6 +200,18 @@ const mtu = await ble.requestMTU(deviceId, 256); // Request MTU size
|
|
|
189
200
|
const newMTU = ble.requestMTU(deviceId, 247);
|
|
190
201
|
console.log('MTU set to:', newMTU);
|
|
191
202
|
|
|
203
|
+
// Request Android connection priority while connected (Android only).
|
|
204
|
+
// Returns true if Android successfully initiated the request (the priority change
|
|
205
|
+
// is applied asynchronously); returns false on iOS, an error, or a disconnected device.
|
|
206
|
+
const priorityRequested = ble.requestConnectionPriority(deviceId, AndroidConnectionPriority.High);
|
|
207
|
+
|
|
208
|
+
// AndroidConnectionPriority options:
|
|
209
|
+
// AndroidConnectionPriority.High → lower latency, higher power usage
|
|
210
|
+
// AndroidConnectionPriority.Balanced → default balanced connection interval
|
|
211
|
+
// AndroidConnectionPriority.LowPower → higher latency, lower power usage
|
|
212
|
+
ble.requestConnectionPriority(deviceId, AndroidConnectionPriority.Balanced);
|
|
213
|
+
ble.requestConnectionPriority(deviceId, AndroidConnectionPriority.LowPower);
|
|
214
|
+
|
|
192
215
|
// Read RSSI value
|
|
193
216
|
const rssi = await ble.readRSSI(deviceId);
|
|
194
217
|
console.log('Current RSSI:', rssi);
|
|
@@ -311,6 +334,7 @@ const deviceId = await ble.connect(
|
|
|
311
334
|
},
|
|
312
335
|
autoConnectOnAndroid,
|
|
313
336
|
);
|
|
337
|
+
ble.requestConnectionPriority(deviceId, AndroidConnectionPriority.High);
|
|
314
338
|
await ble.discoverServices(deviceId);
|
|
315
339
|
|
|
316
340
|
const subscription = await ble.subscribeToCharacteristic(
|
|
@@ -427,8 +451,30 @@ interface BLEDevice {
|
|
|
427
451
|
name: string;
|
|
428
452
|
rssi: number;
|
|
429
453
|
manufacturerData: ManufacturerData;
|
|
454
|
+
serviceData: ServiceData;
|
|
430
455
|
serviceUUIDs: string[];
|
|
431
456
|
isConnectable: boolean;
|
|
457
|
+
isConnected: boolean;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Advertised manufacturer-specific data, keyed by company identifier
|
|
461
|
+
interface ManufacturerData {
|
|
462
|
+
companyIdentifiers: ManufacturerDataEntry[];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
interface ManufacturerDataEntry {
|
|
466
|
+
id: string; // company identifier (hex)
|
|
467
|
+
data: ArrayBuffer; // raw manufacturer payload
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Advertised service data, keyed by service UUID
|
|
471
|
+
interface ServiceData {
|
|
472
|
+
services: ServiceDataEntry[];
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
interface ServiceDataEntry {
|
|
476
|
+
uuid: string; // service UUID
|
|
477
|
+
data: ArrayBuffer; // raw service-data payload
|
|
432
478
|
}
|
|
433
479
|
|
|
434
480
|
interface ScanFilter {
|
|
@@ -462,6 +508,12 @@ enum AndroidScanMode {
|
|
|
462
508
|
Opportunistic = 'Opportunistic', // Only when other apps are scanning
|
|
463
509
|
}
|
|
464
510
|
|
|
511
|
+
enum AndroidConnectionPriority {
|
|
512
|
+
Balanced = 'Balanced', // Balanced connection interval
|
|
513
|
+
High = 'High', // Lower latency, higher power
|
|
514
|
+
LowPower = 'LowPower', // Lower power, higher latency
|
|
515
|
+
}
|
|
516
|
+
|
|
465
517
|
// Callback types
|
|
466
518
|
type StateChangeCallback = (state: BLEState) => void;
|
|
467
519
|
type ScanEventCallback = (device: BLEDevice) => void;
|
package/android/build.gradle
CHANGED
|
@@ -111,6 +111,10 @@ android {
|
|
|
111
111
|
targetCompatibility JavaVersion.VERSION_1_8
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
testOptions {
|
|
115
|
+
unitTests.returnDefaultValues = true
|
|
116
|
+
}
|
|
117
|
+
|
|
114
118
|
sourceSets {
|
|
115
119
|
main {
|
|
116
120
|
if (isNewArchitectureEnabled()) {
|
|
@@ -137,4 +141,6 @@ dependencies {
|
|
|
137
141
|
|
|
138
142
|
// Add a dependency on NitroModules
|
|
139
143
|
implementation project(":react-native-nitro-modules")
|
|
144
|
+
|
|
145
|
+
testImplementation "junit:junit:4.13.2"
|
|
140
146
|
}
|
|
@@ -69,6 +69,17 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
|
|
|
69
69
|
|
|
70
70
|
// Descriptor write callback storage (key: deviceId:descriptorUuid)
|
|
71
71
|
private val descriptorWriteCallbacks = ConcurrentHashMap<String, (Boolean, String) -> Unit>()
|
|
72
|
+
|
|
73
|
+
// Android permits only one outstanding ATT operation per GATT connection.
|
|
74
|
+
// Defer service discovery while an MTU request is in flight so the two
|
|
75
|
+
// procedures cannot overlap when JS calls them back-to-back.
|
|
76
|
+
// All three maps below are guarded by mtuGateLock, so plain HashMaps suffice.
|
|
77
|
+
private val mtuInFlight = HashMap<String, Boolean>()
|
|
78
|
+
private val pendingDiscovery = HashMap<String, () -> Unit>()
|
|
79
|
+
private val mtuFallbackRunnables = HashMap<String, Runnable>()
|
|
80
|
+
private val mtuHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
81
|
+
private val mtuGateLock = Any()
|
|
82
|
+
private val mtuSettleFallbackMs = 1000L
|
|
72
83
|
|
|
73
84
|
// Helper class to store device callbacks
|
|
74
85
|
private data class DeviceCallbacks(
|
|
@@ -225,14 +236,32 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
|
|
|
225
236
|
ManufacturerData(companyIdentifiers = entries.toTypedArray())
|
|
226
237
|
} ?: ManufacturerData(companyIdentifiers = emptyArray())
|
|
227
238
|
|
|
239
|
+
// Extract service data
|
|
240
|
+
val serviceData = scanRecord?.serviceData?.let { dataMap ->
|
|
241
|
+
val entries = mutableListOf<ServiceDataEntry>()
|
|
242
|
+
for ((uuid, value) in dataMap) {
|
|
243
|
+
// Create direct ByteBuffer as required by ArrayBuffer.wrap()
|
|
244
|
+
val directBuffer = java.nio.ByteBuffer.allocateDirect(value.size)
|
|
245
|
+
directBuffer.put(value)
|
|
246
|
+
directBuffer.flip()
|
|
247
|
+
|
|
248
|
+
entries.add(ServiceDataEntry(
|
|
249
|
+
uuid = uuid.toString(),
|
|
250
|
+
data = ArrayBuffer.wrap(directBuffer)
|
|
251
|
+
))
|
|
252
|
+
}
|
|
253
|
+
ServiceData(services = entries.toTypedArray())
|
|
254
|
+
} ?: ServiceData(services = emptyArray())
|
|
255
|
+
|
|
228
256
|
// Extract service UUIDs
|
|
229
257
|
val serviceUUIDs = scanRecord?.serviceUuids?.map { it.toString() }?.toTypedArray() ?: emptyArray()
|
|
230
|
-
|
|
258
|
+
|
|
231
259
|
return BLEDevice(
|
|
232
260
|
id = device.address,
|
|
233
261
|
name = device.name ?: "",
|
|
234
262
|
rssi = scanResult.rssi.toDouble(),
|
|
235
263
|
manufacturerData = manufacturerData,
|
|
264
|
+
serviceData = serviceData,
|
|
236
265
|
serviceUUIDs = serviceUUIDs,
|
|
237
266
|
isConnectable = true, // Assume scannable devices are connectable
|
|
238
267
|
isConnected = false // Scanned devices are not yet connected
|
|
@@ -268,6 +297,7 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
|
|
|
268
297
|
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
|
269
298
|
when (newState) {
|
|
270
299
|
BluetoothProfile.STATE_CONNECTED -> {
|
|
300
|
+
clearMtuGateState(deviceId)
|
|
271
301
|
var connectCb: ((Boolean, String, String) -> Unit)? = null
|
|
272
302
|
deviceCallbacks.compute(deviceId) { _, callbacks ->
|
|
273
303
|
connectCb = callbacks?.connectCallback
|
|
@@ -277,14 +307,25 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
|
|
|
277
307
|
}
|
|
278
308
|
BluetoothProfile.STATE_DISCONNECTED -> {
|
|
279
309
|
// Clean up
|
|
310
|
+
clearMtuGateState(deviceId)
|
|
280
311
|
connectedDevices.remove(deviceId)
|
|
281
312
|
val interrupted = status != BluetoothGatt.GATT_SUCCESS
|
|
282
313
|
val cb = deviceCallbacks.remove(deviceId)
|
|
314
|
+
// If a discoverServices() call was still in flight (or deferred
|
|
315
|
+
// behind an MTU request), it will never receive onServicesDiscovered
|
|
316
|
+
// now. Fail it explicitly so the JS promise does not hang forever.
|
|
317
|
+
cb?.serviceDiscoveryCallback?.invoke(false, "Disconnected before service discovery completed")
|
|
283
318
|
cb?.disconnectCallback?.invoke(deviceId, interrupted, if (interrupted) "Connection lost" else "")
|
|
284
319
|
gatt.close()
|
|
285
320
|
}
|
|
286
321
|
}
|
|
287
322
|
}
|
|
323
|
+
|
|
324
|
+
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
|
|
325
|
+
// Use the deviceId captured for this callback so the gate key always
|
|
326
|
+
// matches the one markMtuInFlight() recorded under.
|
|
327
|
+
settleMtu(deviceId)
|
|
328
|
+
}
|
|
288
329
|
|
|
289
330
|
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
|
290
331
|
val callbacks = deviceCallbacks[deviceId]
|
|
@@ -397,6 +438,59 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
|
|
|
397
438
|
}
|
|
398
439
|
}
|
|
399
440
|
|
|
441
|
+
private fun runOrDeferDiscovery(deviceId: String, startDiscovery: () -> Unit) {
|
|
442
|
+
val shouldStartNow = synchronized(mtuGateLock) {
|
|
443
|
+
if (mtuInFlight[deviceId] == true) {
|
|
444
|
+
pendingDiscovery[deviceId] = startDiscovery
|
|
445
|
+
false
|
|
446
|
+
} else {
|
|
447
|
+
true
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (shouldStartNow) {
|
|
452
|
+
startDiscovery()
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private fun markMtuInFlight(deviceId: String) {
|
|
457
|
+
synchronized(mtuGateLock) {
|
|
458
|
+
mtuInFlight[deviceId] = true
|
|
459
|
+
mtuFallbackRunnables.remove(deviceId)?.let { mtuHandler.removeCallbacks(it) }
|
|
460
|
+
val runnable = Runnable { settleMtu(deviceId) }
|
|
461
|
+
mtuFallbackRunnables[deviceId] = runnable
|
|
462
|
+
mtuHandler.postDelayed(runnable, mtuSettleFallbackMs)
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private fun settleMtu(deviceId: String) {
|
|
467
|
+
val discoveryToStart = synchronized(mtuGateLock) {
|
|
468
|
+
mtuInFlight.remove(deviceId)
|
|
469
|
+
mtuFallbackRunnables.remove(deviceId)?.let { mtuHandler.removeCallbacks(it) }
|
|
470
|
+
pendingDiscovery.remove(deviceId)
|
|
471
|
+
}
|
|
472
|
+
// settleMtu can be invoked from the binder thread (onMtuChanged) or the main
|
|
473
|
+
// thread (fallback). Start the deferred discovery on the main looper so GATT
|
|
474
|
+
// operations are always issued from a single, consistent thread.
|
|
475
|
+
discoveryToStart?.let { mtuHandler.post(it) }
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private fun clearMtuGateState(deviceId: String) {
|
|
479
|
+
synchronized(mtuGateLock) {
|
|
480
|
+
mtuInFlight.remove(deviceId)
|
|
481
|
+
pendingDiscovery.remove(deviceId)
|
|
482
|
+
mtuFallbackRunnables.remove(deviceId)?.let { mtuHandler.removeCallbacks(it) }
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private fun mapAndroidConnectionPriority(androidConnectionPriority: AndroidConnectionPriority): Int {
|
|
487
|
+
return when (androidConnectionPriority) {
|
|
488
|
+
AndroidConnectionPriority.HIGH -> BluetoothGatt.CONNECTION_PRIORITY_HIGH
|
|
489
|
+
AndroidConnectionPriority.LOWPOWER -> BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER
|
|
490
|
+
AndroidConnectionPriority.BALANCED -> BluetoothGatt.CONNECTION_PRIORITY_BALANCED
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
400
494
|
/**
|
|
401
495
|
* Enqueue a GATT operation to be executed sequentially.
|
|
402
496
|
* Android BLE requires that only one GATT operation runs at a time.
|
|
@@ -688,6 +782,9 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
|
|
|
688
782
|
val gatt = connectedDevices[deviceId]
|
|
689
783
|
if (gatt != null) {
|
|
690
784
|
val success = gatt.requestMtu(mtu.toInt())
|
|
785
|
+
if (success) {
|
|
786
|
+
markMtuInFlight(deviceId)
|
|
787
|
+
}
|
|
691
788
|
if (success) mtu else 0.0
|
|
692
789
|
} else {
|
|
693
790
|
0.0
|
|
@@ -697,6 +794,15 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
|
|
|
697
794
|
}
|
|
698
795
|
}
|
|
699
796
|
|
|
797
|
+
override fun requestConnectionPriority(deviceId: String, androidConnectionPriority: AndroidConnectionPriority): Boolean {
|
|
798
|
+
return try {
|
|
799
|
+
val gatt = connectedDevices[deviceId] ?: return false
|
|
800
|
+
gatt.requestConnectionPriority(mapAndroidConnectionPriority(androidConnectionPriority))
|
|
801
|
+
} catch (_: Exception) {
|
|
802
|
+
false
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
700
806
|
override fun readRSSI(deviceId: String, callback: (success: Boolean, rssi: Double, error: String) -> Unit) {
|
|
701
807
|
try {
|
|
702
808
|
val gatt = connectedDevices[deviceId]
|
|
@@ -731,14 +837,16 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
|
|
|
731
837
|
// Store the callback for when service discovery completes
|
|
732
838
|
callbacks.serviceDiscoveryCallback = callback
|
|
733
839
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
840
|
+
runOrDeferDiscovery(deviceId) {
|
|
841
|
+
// Start service discovery
|
|
842
|
+
val success = gatt.discoverServices()
|
|
843
|
+
if (!success) {
|
|
844
|
+
// Clear callback and report failure immediately
|
|
845
|
+
callbacks.serviceDiscoveryCallback = null
|
|
846
|
+
callback(false, "Failed to start service discovery")
|
|
847
|
+
}
|
|
848
|
+
// If success, the callback will be invoked in onServicesDiscovered
|
|
740
849
|
}
|
|
741
|
-
// If success, the callback will be invoked in onServicesDiscovered
|
|
742
850
|
} else {
|
|
743
851
|
callback(false, "Device callback not found")
|
|
744
852
|
}
|
|
@@ -921,7 +1029,16 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
|
|
|
921
1029
|
|
|
922
1030
|
val subscriptionKey = "$serviceId:$characteristicId"
|
|
923
1031
|
|
|
924
|
-
|
|
1032
|
+
val cccdValue = when (cccdSubscriptionMode(characteristic.properties)) {
|
|
1033
|
+
CccdSubscriptionMode.INDICATE -> BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
|
|
1034
|
+
CccdSubscriptionMode.NOTIFY -> BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
|
1035
|
+
CccdSubscriptionMode.UNSUPPORTED -> {
|
|
1036
|
+
completionCallback(false, "Characteristic does not support notify or indicate")
|
|
1037
|
+
return
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Write to the CCCD descriptor to enable notifications or indications on the remote device.
|
|
925
1042
|
// The update callback is stored only after the descriptor write succeeds
|
|
926
1043
|
// so that isSubscribedToCharacteristic reflects actual BLE state.
|
|
927
1044
|
val descriptor = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
|
|
@@ -930,7 +1047,7 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
|
|
|
930
1047
|
GattOperation.WriteDescriptor(
|
|
931
1048
|
gatt = gatt,
|
|
932
1049
|
descriptor = descriptor,
|
|
933
|
-
value =
|
|
1050
|
+
value = cccdValue,
|
|
934
1051
|
deviceId = deviceId,
|
|
935
1052
|
callback = { success, error ->
|
|
936
1053
|
if (success) {
|
|
@@ -1142,4 +1259,4 @@ class BleNitroBleManager : HybridNativeBleNitroSpec() {
|
|
|
1142
1259
|
}
|
|
1143
1260
|
return promise
|
|
1144
1261
|
}
|
|
1145
|
-
}
|
|
1262
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
package com.margelo.nitro.co.zyke.ble
|
|
2
|
+
|
|
3
|
+
import android.bluetooth.BluetoothGattCharacteristic
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The delivery mode to request via the CCCD descriptor when subscribing to a
|
|
7
|
+
* characteristic, derived from the characteristic's declared properties.
|
|
8
|
+
*/
|
|
9
|
+
internal enum class CccdSubscriptionMode {
|
|
10
|
+
/** Use ENABLE_NOTIFICATION_VALUE (unacknowledged delivery). */
|
|
11
|
+
NOTIFY,
|
|
12
|
+
|
|
13
|
+
/** Use ENABLE_INDICATION_VALUE (acknowledged delivery). */
|
|
14
|
+
INDICATE,
|
|
15
|
+
|
|
16
|
+
/** The characteristic supports neither notify nor indicate. */
|
|
17
|
+
UNSUPPORTED,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Selects the CCCD subscription mode for a characteristic's [properties] bitmask.
|
|
22
|
+
*
|
|
23
|
+
* Notification is preferred when the characteristic supports both notify and
|
|
24
|
+
* indicate because it is unacknowledged and lower-overhead. Indication is used
|
|
25
|
+
* only for indicate-only characteristics. This mirrors iOS CoreBluetooth, where
|
|
26
|
+
* `setNotifyValue(true)` automatically selects the appropriate delivery mode.
|
|
27
|
+
*
|
|
28
|
+
* [properties] is the value of [BluetoothGattCharacteristic.getProperties].
|
|
29
|
+
*/
|
|
30
|
+
internal fun cccdSubscriptionMode(properties: Int): CccdSubscriptionMode {
|
|
31
|
+
val supportsNotify = (properties and BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0
|
|
32
|
+
val supportsIndicate = (properties and BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0
|
|
33
|
+
return when {
|
|
34
|
+
supportsNotify -> CccdSubscriptionMode.NOTIFY
|
|
35
|
+
supportsIndicate -> CccdSubscriptionMode.INDICATE
|
|
36
|
+
else -> CccdSubscriptionMode.UNSUPPORTED
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
package com.margelo.nitro.co.zyke.ble
|
|
2
|
+
|
|
3
|
+
import android.bluetooth.BluetoothGattCharacteristic
|
|
4
|
+
import org.junit.Assert.assertEquals
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Unit tests for [cccdSubscriptionMode].
|
|
9
|
+
*
|
|
10
|
+
* `PROPERTY_NOTIFY` and `PROPERTY_INDICATE` are compile-time `int` constants and
|
|
11
|
+
* are inlined by the compiler, so these tests run on the plain JVM without
|
|
12
|
+
* Robolectric or a connected device.
|
|
13
|
+
*/
|
|
14
|
+
class CccdSubscriptionTest {
|
|
15
|
+
private val notify = BluetoothGattCharacteristic.PROPERTY_NOTIFY
|
|
16
|
+
private val indicate = BluetoothGattCharacteristic.PROPERTY_INDICATE
|
|
17
|
+
private val read = BluetoothGattCharacteristic.PROPERTY_READ
|
|
18
|
+
|
|
19
|
+
@Test
|
|
20
|
+
fun notifyOnly_usesNotify() {
|
|
21
|
+
assertEquals(CccdSubscriptionMode.NOTIFY, cccdSubscriptionMode(notify))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@Test
|
|
25
|
+
fun indicateOnly_usesIndicate() {
|
|
26
|
+
assertEquals(CccdSubscriptionMode.INDICATE, cccdSubscriptionMode(indicate))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@Test
|
|
30
|
+
fun supportsBoth_prefersNotify() {
|
|
31
|
+
assertEquals(CccdSubscriptionMode.NOTIFY, cccdSubscriptionMode(notify or indicate))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Test
|
|
35
|
+
fun indicateWithOtherProperties_usesIndicate() {
|
|
36
|
+
assertEquals(CccdSubscriptionMode.INDICATE, cccdSubscriptionMode(indicate or read))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Test
|
|
40
|
+
fun neitherNotifyNorIndicate_isUnsupported() {
|
|
41
|
+
assertEquals(CccdSubscriptionMode.UNSUPPORTED, cccdSubscriptionMode(read))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Test
|
|
45
|
+
fun noProperties_isUnsupported() {
|
|
46
|
+
assertEquals(CccdSubscriptionMode.UNSUPPORTED, cccdSubscriptionMode(0))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -164,6 +164,7 @@ public class BleNitroBleManager: HybridNativeBleNitroSpec {
|
|
|
164
164
|
name: peripheral.name ?? "Unknown Device",
|
|
165
165
|
rssi: 0, // RSSI not available for connected devices without explicit read
|
|
166
166
|
manufacturerData: ManufacturerData(companyIdentifiers: []), // Not available for connected devices
|
|
167
|
+
serviceData: ServiceData(services: []), // Not available for connected devices
|
|
167
168
|
serviceUUIDs: peripheral.services?.map { $0.uuid.uuidString } ?? [],
|
|
168
169
|
isConnectable: true, // Already connected, so it was connectable
|
|
169
170
|
isConnected: true
|
|
@@ -185,12 +186,13 @@ public class BleNitroBleManager: HybridNativeBleNitroSpec {
|
|
|
185
186
|
name: peripheral.name ?? "Unknown Device",
|
|
186
187
|
rssi: 0,
|
|
187
188
|
manufacturerData: ManufacturerData(companyIdentifiers: []),
|
|
189
|
+
serviceData: ServiceData(services: []),
|
|
188
190
|
serviceUUIDs: peripheral.services?.map { $0.uuid.uuidString } ?? [],
|
|
189
191
|
isConnectable: true,
|
|
190
192
|
isConnected: true
|
|
191
193
|
)
|
|
192
194
|
connectedDevices.append(device)
|
|
193
|
-
|
|
195
|
+
|
|
194
196
|
// Add to our tracking dictionary
|
|
195
197
|
connectedPeripherals[deviceId] = peripheral
|
|
196
198
|
}
|
|
@@ -211,6 +213,7 @@ public class BleNitroBleManager: HybridNativeBleNitroSpec {
|
|
|
211
213
|
name: peripheral.name ?? "Unknown Device",
|
|
212
214
|
rssi: 0,
|
|
213
215
|
manufacturerData: ManufacturerData(companyIdentifiers: []),
|
|
216
|
+
serviceData: ServiceData(services: []),
|
|
214
217
|
serviceUUIDs: peripheral.services?.map { $0.uuid.uuidString } ?? [],
|
|
215
218
|
isConnectable: true,
|
|
216
219
|
isConnected: true
|
|
@@ -289,6 +292,10 @@ public class BleNitroBleManager: HybridNativeBleNitroSpec {
|
|
|
289
292
|
|
|
290
293
|
return Double(peripheral.maximumWriteValueLength(for: .withoutResponse))
|
|
291
294
|
}
|
|
295
|
+
|
|
296
|
+
public func requestConnectionPriority(deviceId: String, androidConnectionPriority: AndroidConnectionPriority) throws -> Bool {
|
|
297
|
+
return false
|
|
298
|
+
}
|
|
292
299
|
|
|
293
300
|
public func readRSSI(deviceId: String, callback: @escaping (Bool, Double, String) -> Void) throws {
|
|
294
301
|
guard let peripheral = connectedPeripherals[deviceId] else {
|
|
@@ -607,6 +614,7 @@ public class BleNitroBleManager: HybridNativeBleNitroSpec {
|
|
|
607
614
|
name: peripheral.name ?? "Restored Device",
|
|
608
615
|
rssi: 0, // RSSI not available for restored peripherals
|
|
609
616
|
manufacturerData: ManufacturerData(companyIdentifiers: []),
|
|
617
|
+
serviceData: ServiceData(services: []),
|
|
610
618
|
serviceUUIDs: peripheral.services?.map { $0.uuid.uuidString } ?? [],
|
|
611
619
|
isConnectable: true,
|
|
612
620
|
isConnected: peripheral.state == .connected
|
|
@@ -726,20 +734,37 @@ public class BleNitroBleManager: HybridNativeBleNitroSpec {
|
|
|
726
734
|
|
|
727
735
|
// Extract manufacturer data
|
|
728
736
|
let manufacturerData = extractManufacturerData(from: advertisementData)
|
|
729
|
-
|
|
737
|
+
|
|
738
|
+
// Extract service data
|
|
739
|
+
let serviceData = extractServiceData(from: advertisementData)
|
|
740
|
+
|
|
730
741
|
// Check if connectable
|
|
731
742
|
let isConnectable = (advertisementData[CBAdvertisementDataIsConnectable] as? Bool) ?? true
|
|
732
|
-
|
|
743
|
+
|
|
733
744
|
return BLEDevice(
|
|
734
745
|
id: deviceId,
|
|
735
746
|
name: deviceName,
|
|
736
747
|
rssi: rssi,
|
|
737
748
|
manufacturerData: manufacturerData,
|
|
749
|
+
serviceData: serviceData,
|
|
738
750
|
serviceUUIDs: serviceUUIDs,
|
|
739
751
|
isConnectable: isConnectable,
|
|
740
752
|
isConnected: peripheral.state == .connected
|
|
741
753
|
)
|
|
742
754
|
}
|
|
755
|
+
|
|
756
|
+
private func extractServiceData(from advertisementData: [String: Any]) -> ServiceData {
|
|
757
|
+
guard let serviceDataRaw = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data] else {
|
|
758
|
+
return ServiceData(services: [])
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
let entries: [ServiceDataEntry] = serviceDataRaw.map { (uuid, data) in
|
|
762
|
+
let arrayBuffer = (try? ArrayBuffer.copy(data: data)) ?? (try! ArrayBuffer.copy(data: Data()))
|
|
763
|
+
return ServiceDataEntry(uuid: uuid.uuidString, data: arrayBuffer)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return ServiceData(services: entries)
|
|
767
|
+
}
|
|
743
768
|
|
|
744
769
|
private func extractManufacturerData(from advertisementData: [String: Any]) -> ManufacturerData {
|
|
745
770
|
guard let manufacturerDataRaw = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data else {
|
package/lib/commonjs/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { type ByteArray, type ScanFilter, type BLEDevice, type ScanCallback, type ManufacturerDataEntry, type ManufacturerData, type ConnectionCallback, type DisconnectEventCallback, type OperationCallback, type CharacteristicUpdateCallback, type Subscription, type AsyncSubscription, type BleNitroManagerOptions, BLEState, AndroidScanMode, BleNitroManager, BleTimeoutError, } from "./manager";
|
|
1
|
+
export { type ByteArray, type ScanFilter, type BLEDevice, type ScanCallback, type ManufacturerDataEntry, type ManufacturerData, type ServiceDataEntry, type ServiceData, type ConnectionCallback, type DisconnectEventCallback, type OperationCallback, type CharacteristicUpdateCallback, type Subscription, type AsyncSubscription, type BleNitroManagerOptions, BLEState, AndroidScanMode, AndroidConnectionPriority, BleNitroManager, BleTimeoutError, } from "./manager";
|
|
2
2
|
export { BleNitro } from './singleton';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,KAAK,4BAA4B,EACjC,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,QAAQ,EACR,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC1B,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,EAC5B,KAAK,iBAAiB,EACtB,KAAK,4BAA4B,EACjC,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,QAAQ,EACR,eAAe,EACf,yBAAyB,EACzB,eAAe,EACf,eAAe,GAChB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
|
package/lib/commonjs/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BleNitro = exports.BleTimeoutError = exports.BleNitroManager = exports.AndroidScanMode = exports.BLEState = void 0;
|
|
3
|
+
exports.BleNitro = exports.BleTimeoutError = exports.BleNitroManager = exports.AndroidConnectionPriority = exports.AndroidScanMode = exports.BLEState = void 0;
|
|
4
4
|
var manager_1 = require("./manager");
|
|
5
5
|
Object.defineProperty(exports, "BLEState", { enumerable: true, get: function () { return manager_1.BLEState; } });
|
|
6
6
|
Object.defineProperty(exports, "AndroidScanMode", { enumerable: true, get: function () { return manager_1.AndroidScanMode; } });
|
|
7
|
+
Object.defineProperty(exports, "AndroidConnectionPriority", { enumerable: true, get: function () { return manager_1.AndroidConnectionPriority; } });
|
|
7
8
|
Object.defineProperty(exports, "BleNitroManager", { enumerable: true, get: function () { return manager_1.BleNitroManager; } });
|
|
8
9
|
Object.defineProperty(exports, "BleTimeoutError", { enumerable: true, get: function () { return manager_1.BleTimeoutError; } });
|
|
9
10
|
var singleton_1 = require("./singleton");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,qCAqBmB;AALjB,mGAAA,QAAQ,OAAA;AACR,0GAAA,eAAe,OAAA;AACf,oHAAA,yBAAyB,OAAA;AACzB,0GAAA,eAAe,OAAA;AACf,0GAAA,eAAe,OAAA;AAGjB,yCAAuC;AAA9B,qGAAA,QAAQ,OAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BLEDevice as NativeBLEDevice, BLEState as NativeBLEState, AndroidScanMode as NativeAndroidScanMode } from './specs/NativeBleNitro';
|
|
1
|
+
import { BLEDevice as NativeBLEDevice, BLEState as NativeBLEState, AndroidScanMode as NativeAndroidScanMode, AndroidConnectionPriority as NativeAndroidConnectionPriority } from './specs/NativeBleNitro';
|
|
2
2
|
export declare class BleTimeoutError extends Error {
|
|
3
3
|
constructor(operation: string, ms: number);
|
|
4
4
|
}
|
|
@@ -16,11 +16,19 @@ export interface ManufacturerDataEntry {
|
|
|
16
16
|
export interface ManufacturerData {
|
|
17
17
|
companyIdentifiers: ManufacturerDataEntry[];
|
|
18
18
|
}
|
|
19
|
+
export interface ServiceDataEntry {
|
|
20
|
+
uuid: string;
|
|
21
|
+
data: ByteArray;
|
|
22
|
+
}
|
|
23
|
+
export interface ServiceData {
|
|
24
|
+
services: ServiceDataEntry[];
|
|
25
|
+
}
|
|
19
26
|
export interface BLEDevice {
|
|
20
27
|
id: string;
|
|
21
28
|
name: string;
|
|
22
29
|
rssi: number;
|
|
23
30
|
manufacturerData: ManufacturerData;
|
|
31
|
+
serviceData: ServiceData;
|
|
24
32
|
serviceUUIDs: string[];
|
|
25
33
|
isConnectable: boolean;
|
|
26
34
|
isConnected: boolean;
|
|
@@ -51,12 +59,18 @@ export declare enum AndroidScanMode {
|
|
|
51
59
|
LowPower = "LowPower",
|
|
52
60
|
Opportunistic = "Opportunistic"
|
|
53
61
|
}
|
|
62
|
+
export declare enum AndroidConnectionPriority {
|
|
63
|
+
Balanced = "Balanced",
|
|
64
|
+
High = "High",
|
|
65
|
+
LowPower = "LowPower"
|
|
66
|
+
}
|
|
54
67
|
export type BleNitroManagerOptions = {
|
|
55
68
|
restoreIdentifier?: string;
|
|
56
69
|
onRestoredState?: RestoreStateCallback;
|
|
57
70
|
};
|
|
58
71
|
export declare function mapNativeBLEStateToBLEState(nativeState: NativeBLEState): BLEState;
|
|
59
72
|
export declare function mapAndroidScanModeToNativeAndroidScanMode(scanMode: AndroidScanMode): NativeAndroidScanMode;
|
|
73
|
+
export declare function mapAndroidConnectionPriorityToNativeAndroidConnectionPriority(priority: AndroidConnectionPriority): NativeAndroidConnectionPriority;
|
|
60
74
|
export declare function convertNativeBleDeviceToBleDevice(nativeBleDevice: NativeBLEDevice): BLEDevice;
|
|
61
75
|
export declare function arrayBufferToByteArray(buffer: ArrayBuffer): ByteArray;
|
|
62
76
|
export declare function byteArrayToArrayBuffer(data: ByteArray): ArrayBuffer;
|
|
@@ -150,6 +164,15 @@ export declare class BleNitroManager {
|
|
|
150
164
|
* @returns On Android: new MTU size; on iOS: current MTU size as it is handled by iOS itself; on error: -1
|
|
151
165
|
*/
|
|
152
166
|
requestMTU(deviceId: string, mtu: number): number;
|
|
167
|
+
/**
|
|
168
|
+
* Request an Android connection priority for an active connection.
|
|
169
|
+
* @param deviceId ID of the device
|
|
170
|
+
* @param priority Desired Android connection priority
|
|
171
|
+
* @returns On Android: true if the request was successfully initiated (the
|
|
172
|
+
* priority change itself is applied asynchronously); on iOS, an error, or a
|
|
173
|
+
* disconnected device: false
|
|
174
|
+
*/
|
|
175
|
+
requestConnectionPriority(deviceId: string, priority: AndroidConnectionPriority): boolean;
|
|
153
176
|
/**
|
|
154
177
|
* Read RSSI for a connected device
|
|
155
178
|
* @param deviceId ID of the device
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/manager.ts"],"names":[],"mappings":"AACA,OAAO,EAEL,SAAS,IAAI,eAAe,EAC5B,QAAQ,IAAI,cAAc,EAE1B,eAAe,IAAI,qBAAqB,
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../src/manager.ts"],"names":[],"mappings":"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"}
|