munim-bluetooth 0.2.2 → 0.2.4
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/MunimBluetooth.podspec
CHANGED
|
@@ -22,8 +22,13 @@ Pod::Spec.new do |s|
|
|
|
22
22
|
"cpp/**/*.{hpp,cpp}",
|
|
23
23
|
]
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
autolinking_script = File.join(__dir__, "nitrogen/generated/ios/MunimBluetooth+autolinking.rb")
|
|
26
|
+
if File.exist?(autolinking_script)
|
|
27
|
+
load autolinking_script
|
|
28
|
+
add_nitrogen_files(s)
|
|
29
|
+
else
|
|
30
|
+
Pod::UI.puts "[MunimBluetooth] Skipping Nitro autolinking – #{autolinking_script} not found"
|
|
31
|
+
end
|
|
27
32
|
|
|
28
33
|
s.dependency 'React-jsi'
|
|
29
34
|
s.dependency 'React-callinvoker'
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
package com.munimbluetooth
|
|
2
2
|
|
|
3
|
-
import com.margelo.nitro.munimbluetooth.HybridMunimBluetoothSpec
|
|
4
3
|
import android.bluetooth.*
|
|
5
4
|
import android.bluetooth.le.*
|
|
6
5
|
import android.content.Context
|
|
6
|
+
import android.os.Build
|
|
7
7
|
import android.os.ParcelUuid
|
|
8
8
|
import android.util.Log
|
|
9
|
+
import com.facebook.react.bridge.Arguments
|
|
10
|
+
import com.facebook.react.bridge.WritableArray
|
|
11
|
+
import com.facebook.react.bridge.WritableMap
|
|
12
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
13
|
+
import com.margelo.nitro.NitroModules
|
|
14
|
+
import com.margelo.nitro.munimbluetooth.HybridMunimBluetoothSpec
|
|
9
15
|
import kotlinx.coroutines.*
|
|
10
16
|
import java.util.*
|
|
11
17
|
|
|
@@ -28,6 +34,7 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
28
34
|
private val discoveredDevices = mutableMapOf<String, BluetoothDevice>()
|
|
29
35
|
private val connectedDevices = mutableMapOf<String, BluetoothGatt>()
|
|
30
36
|
private val deviceCharacteristics = mutableMapOf<String, MutableList<BluetoothGattCharacteristic>>()
|
|
37
|
+
private val eventEmitter = NitroEventEmitter(TAG)
|
|
31
38
|
|
|
32
39
|
init {
|
|
33
40
|
// Initialize Bluetooth managers - this would need ReactApplicationContext in real implementation
|
|
@@ -35,9 +42,8 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
private fun getBluetoothManager(): BluetoothManager? {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return null
|
|
45
|
+
val context = NitroModules.applicationContext ?: return null
|
|
46
|
+
return context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
private fun ensureBluetoothManager() {
|
|
@@ -317,8 +323,10 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
317
323
|
val deviceId = device.address
|
|
318
324
|
discoveredDevices[deviceId] = device
|
|
319
325
|
|
|
320
|
-
|
|
321
|
-
|
|
326
|
+
eventEmitter.emit(
|
|
327
|
+
"deviceFound",
|
|
328
|
+
buildScanPayload(result)
|
|
329
|
+
)
|
|
322
330
|
}
|
|
323
331
|
|
|
324
332
|
override fun onBatchScanResults(results: MutableList<ScanResult>) {
|
|
@@ -588,6 +596,60 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
588
596
|
// This is a no-op as Nitro modules handle events differently
|
|
589
597
|
}
|
|
590
598
|
|
|
599
|
+
private fun buildScanPayload(result: ScanResult): Map<String, Any?> {
|
|
600
|
+
val record = result.scanRecord
|
|
601
|
+
val manufacturerData = extractManufacturerData(record)
|
|
602
|
+
val serviceUUIDs = record?.serviceUuids?.map { it.uuid.toString() }
|
|
603
|
+
val serviceData = extractServiceData(record)
|
|
604
|
+
val txPower = record?.txPowerLevel?.takeIf { it != Int.MIN_VALUE }
|
|
605
|
+
val advertisementData = mutableMapOf<String, Any?>()
|
|
606
|
+
record?.deviceName?.let { advertisementData["completeLocalName"] = it }
|
|
607
|
+
txPower?.let { advertisementData["txPowerLevel"] = it }
|
|
608
|
+
manufacturerData?.let { advertisementData["manufacturerData"] = it }
|
|
609
|
+
serviceUUIDs?.let { advertisementData["serviceUUIDs"] = it }
|
|
610
|
+
serviceData?.takeIf { it.isNotEmpty() }?.let { advertisementData["serviceData"] = it }
|
|
611
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
612
|
+
advertisementData["isConnectable"] = result.isConnectable
|
|
613
|
+
}
|
|
614
|
+
advertisementData["rssi"] = result.rssi
|
|
615
|
+
|
|
616
|
+
return mapOf(
|
|
617
|
+
"id" to result.device.address,
|
|
618
|
+
"name" to result.device.name,
|
|
619
|
+
"localName" to record?.deviceName,
|
|
620
|
+
"manufacturerData" to manufacturerData,
|
|
621
|
+
"serviceUUIDs" to serviceUUIDs,
|
|
622
|
+
"serviceData" to serviceData,
|
|
623
|
+
"rssi" to result.rssi,
|
|
624
|
+
"txPowerLevel" to txPower,
|
|
625
|
+
"isConnectable" to if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) result.isConnectable else null,
|
|
626
|
+
"advertisementData" to advertisementData
|
|
627
|
+
)
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
private fun extractManufacturerData(record: ScanRecord?): String? {
|
|
631
|
+
val data = record?.manufacturerSpecificData ?: return null
|
|
632
|
+
if (data.size() == 0) return null
|
|
633
|
+
val bytes = data.valueAt(0) ?: return null
|
|
634
|
+
return bytes.toHexString()
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
private fun extractServiceData(record: ScanRecord?): List<Map<String, Any?>>? {
|
|
638
|
+
val map = record?.serviceData ?: return null
|
|
639
|
+
val result = map.entries.mapNotNull { entry ->
|
|
640
|
+
val bytes = entry.value ?: return@mapNotNull null
|
|
641
|
+
mapOf(
|
|
642
|
+
"uuid" to entry.key.uuid.toString(),
|
|
643
|
+
"data" to bytes.toHexString()
|
|
644
|
+
)
|
|
645
|
+
}
|
|
646
|
+
return result.takeIf { it.isNotEmpty() }
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
private fun ByteArray.toHexString(): String {
|
|
650
|
+
return joinToString("") { "%02x".format(it) }
|
|
651
|
+
}
|
|
652
|
+
|
|
591
653
|
// MARK: - Helper Methods
|
|
592
654
|
|
|
593
655
|
private fun processAdvertisingData(
|
|
@@ -704,3 +766,66 @@ class HybridMunimBluetooth : HybridMunimBluetoothSpec() {
|
|
|
704
766
|
gattServerReady = true
|
|
705
767
|
}
|
|
706
768
|
}
|
|
769
|
+
|
|
770
|
+
private class NitroEventEmitter(private val tag: String) {
|
|
771
|
+
fun emit(eventName: String, payload: Map<String, Any?>) {
|
|
772
|
+
val context = NitroModules.applicationContext
|
|
773
|
+
if (context == null) {
|
|
774
|
+
Log.w(tag, "Unable to emit $eventName: React context unavailable")
|
|
775
|
+
return
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
val writable = Arguments.createMap()
|
|
779
|
+
payload.forEach { (key, value) ->
|
|
780
|
+
writeValue(writable, key, value)
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
context
|
|
784
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
785
|
+
.emit(eventName, writable)
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
private fun writeValue(map: WritableMap, key: String, value: Any?) {
|
|
789
|
+
when (value) {
|
|
790
|
+
null -> map.putNull(key)
|
|
791
|
+
is String -> map.putString(key, value)
|
|
792
|
+
is Boolean -> map.putBoolean(key, value)
|
|
793
|
+
is Int -> map.putInt(key, value)
|
|
794
|
+
is Double -> map.putDouble(key, value)
|
|
795
|
+
is Float -> map.putDouble(key, value.toDouble())
|
|
796
|
+
is Long -> map.putDouble(key, value.toDouble())
|
|
797
|
+
is Map<*, *> -> map.putMap(key, convertMap(value))
|
|
798
|
+
is List<*> -> map.putArray(key, convertArray(value))
|
|
799
|
+
else -> map.putString(key, value.toString())
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
private fun convertMap(map: Map<*, *>): WritableMap {
|
|
804
|
+
val writable = Arguments.createMap()
|
|
805
|
+
map.forEach { (key, value) ->
|
|
806
|
+
if (key is String) {
|
|
807
|
+
writeValue(writable, key, value)
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return writable
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private fun convertArray(list: List<*>): WritableArray {
|
|
814
|
+
val writable = Arguments.createArray()
|
|
815
|
+
list.forEach { value ->
|
|
816
|
+
when (value) {
|
|
817
|
+
null -> writable.pushNull()
|
|
818
|
+
is String -> writable.pushString(value)
|
|
819
|
+
is Boolean -> writable.pushBoolean(value)
|
|
820
|
+
is Int -> writable.pushInt(value)
|
|
821
|
+
is Double -> writable.pushDouble(value)
|
|
822
|
+
is Float -> writable.pushDouble(value.toDouble())
|
|
823
|
+
is Long -> writable.pushDouble(value.toDouble())
|
|
824
|
+
is Map<*, *> -> writable.pushMap(convertMap(value))
|
|
825
|
+
is List<*> -> writable.pushArray(convertArray(value))
|
|
826
|
+
else -> writable.pushString(value.toString())
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
return writable
|
|
830
|
+
}
|
|
831
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import Foundation
|
|
9
9
|
import CoreBluetooth
|
|
10
10
|
import ReactNativeNitroModules
|
|
11
|
+
import React
|
|
11
12
|
|
|
12
13
|
class HybridMunimBluetooth: HybridMunimBluetoothSpec {
|
|
13
14
|
// Peripheral Manager
|
|
@@ -627,7 +628,24 @@ class NitroEventEmitter {
|
|
|
627
628
|
}
|
|
628
629
|
|
|
629
630
|
func emit(_ eventName: String, _ body: [String: Any]) {
|
|
630
|
-
|
|
631
|
-
|
|
631
|
+
let sendEvent = {
|
|
632
|
+
guard let bridge = RCTBridge.current() ?? RCTBridge.currentBridge() else {
|
|
633
|
+
NSLog("[\(self.moduleName)] Unable to emit event \(eventName): missing bridge")
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
bridge.enqueueJSCall(
|
|
638
|
+
"RCTDeviceEventEmitter",
|
|
639
|
+
method: "emit",
|
|
640
|
+
args: [eventName, body],
|
|
641
|
+
completion: nil
|
|
642
|
+
)
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
if Thread.isMainThread {
|
|
646
|
+
sendEvent()
|
|
647
|
+
} else {
|
|
648
|
+
DispatchQueue.main.async(execute: sendEvent)
|
|
649
|
+
}
|
|
632
650
|
}
|
|
633
651
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "munim-bluetooth",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "A comprehensive React Native library for all your Bluetooth Low Energy (BLE) needs, supporting both peripheral and central roles with Expo support",
|
|
5
5
|
"main": "./lib/commonjs/index.js",
|
|
6
6
|
"module": "./lib/module/index.js",
|