@magicred-1/react-native-lxmf 0.2.29 → 0.2.31
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/android/src/main/jniLibs/arm64-v8a/liblxmf_rn.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/liblxmf_rn.so +0 -0
- package/android/src/main/jniLibs/x86_64/liblxmf_rn.so +0 -0
- package/android/src/main/kotlin/expo/modules/lxmf/BleManager.kt +0 -7
- package/android/src/main/kotlin/expo/modules/lxmf/LxmfModule.kt +17 -3
- package/android/src/main/kotlin/expo/modules/lxmf/NusManager.kt +283 -0
- package/ios/RustCore/liblxmf_rn.xcframework/Info.plist +5 -5
- package/package.json +1 -1
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -48,8 +48,6 @@ class BleManager(
|
|
|
48
48
|
private val connecting = mutableSetOf<String>()
|
|
49
49
|
// Timestamp (ms) when each MAC last disconnected — enforces reconnect cooldown
|
|
50
50
|
private val disconnectedAt = mutableMapOf<String, Long>()
|
|
51
|
-
// MACs seen in scan results but not yet GATT-connected (mirrors iOS discoveredUnpairedRNodes)
|
|
52
|
-
private val discoveredUnpaired = mutableSetOf<String>()
|
|
53
51
|
|
|
54
52
|
private var scanner: BluetoothLeScanner? = null
|
|
55
53
|
private var advertiser: BluetoothLeAdvertiser? = null
|
|
@@ -115,12 +113,10 @@ class BleManager(
|
|
|
115
113
|
connections.values.forEach { it.disconnect(); it.close() }
|
|
116
114
|
connections.clear()
|
|
117
115
|
connecting.clear()
|
|
118
|
-
discoveredUnpaired.clear()
|
|
119
116
|
Log.i(TAG, "BleManager stopped")
|
|
120
117
|
}
|
|
121
118
|
|
|
122
119
|
fun connectedPeerCount(): Int = module.nativeBlePeerCount()
|
|
123
|
-
fun unpairedRNodeCount(): Int = discoveredUnpaired.size
|
|
124
120
|
|
|
125
121
|
// ── Advertising (so peers can find us) ───────────────────────────────────
|
|
126
122
|
|
|
@@ -396,7 +392,6 @@ class BleManager(
|
|
|
396
392
|
val lastDisconnect = disconnectedAt[mac] ?: 0L
|
|
397
393
|
if (System.currentTimeMillis() - lastDisconnect < RECONNECT_COOLDOWN_MS) return
|
|
398
394
|
Log.i(TAG, "BLE: found peer $mac, connecting")
|
|
399
|
-
discoveredUnpaired.add(mac)
|
|
400
395
|
connecting.add(mac)
|
|
401
396
|
device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
|
402
397
|
}
|
|
@@ -418,7 +413,6 @@ class BleManager(
|
|
|
418
413
|
Log.i(TAG, "BLE GATT connected: $mac")
|
|
419
414
|
connections[mac] = gatt
|
|
420
415
|
connecting.remove(mac)
|
|
421
|
-
discoveredUnpaired.remove(mac)
|
|
422
416
|
gatt.discoverServices()
|
|
423
417
|
}
|
|
424
418
|
BluetoothProfile.STATE_DISCONNECTED -> {
|
|
@@ -427,7 +421,6 @@ class BleManager(
|
|
|
427
421
|
Log.i(TAG, "BLE GATT disconnected: $mac (status=$status)")
|
|
428
422
|
connections.remove(mac)
|
|
429
423
|
connecting.remove(mac)
|
|
430
|
-
discoveredUnpaired.remove(mac)
|
|
431
424
|
disconnectedAt[mac] = System.currentTimeMillis()
|
|
432
425
|
gatt.close()
|
|
433
426
|
// Notify Rust
|
|
@@ -8,19 +8,20 @@ import expo.modules.kotlin.modules.ModuleDefinition
|
|
|
8
8
|
import org.json.JSONArray
|
|
9
9
|
|
|
10
10
|
private const val TAG = "LxmfModule"
|
|
11
|
-
private const val POLL_INTERVAL_MS =
|
|
11
|
+
private const val POLL_INTERVAL_MS = 80L
|
|
12
12
|
|
|
13
13
|
class LxmfModule : Module() {
|
|
14
14
|
private val pollHandler = Handler(Looper.getMainLooper())
|
|
15
15
|
private val pollRunnable = object : Runnable {
|
|
16
16
|
override fun run() {
|
|
17
17
|
drainAndEmitEvents()
|
|
18
|
+
nusManager?.pollTxAndWrite()
|
|
18
19
|
pollHandler.postDelayed(this, POLL_INTERVAL_MS)
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
// BleManager is created lazily when the app context is available
|
|
23
23
|
private var bleManager: BleManager? = null
|
|
24
|
+
private var nusManager: NusManager? = null
|
|
24
25
|
|
|
25
26
|
override fun definition() = ModuleDefinition {
|
|
26
27
|
Name("LxmfModule")
|
|
@@ -48,6 +49,7 @@ class LxmfModule : Module() {
|
|
|
48
49
|
?: appContext.currentActivity?.applicationContext
|
|
49
50
|
if (ctx != null) {
|
|
50
51
|
bleManager = BleManager(ctx, this@LxmfModule)
|
|
52
|
+
nusManager = NusManager(ctx, this@LxmfModule)
|
|
51
53
|
}
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -55,6 +57,8 @@ class LxmfModule : Module() {
|
|
|
55
57
|
pollHandler.removeCallbacks(pollRunnable)
|
|
56
58
|
bleManager?.stop()
|
|
57
59
|
bleManager = null
|
|
60
|
+
nusManager?.stop()
|
|
61
|
+
nusManager = null
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
// Lifecycle
|
|
@@ -78,10 +82,14 @@ class LxmfModule : Module() {
|
|
|
78
82
|
val rc = nativeStart(identityHex, lxmfAddressHex, mode, announceIntervalMs.toLong(),
|
|
79
83
|
bleMtuHint.toShort(), interfacesJson, displayName, isBeacon)
|
|
80
84
|
if (rc != 0) throw RuntimeException("nativeStart returned $rc")
|
|
85
|
+
bleManager?.start()
|
|
86
|
+
nusManager?.start()
|
|
81
87
|
true
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
AsyncFunction("stop") {
|
|
91
|
+
bleManager?.stop()
|
|
92
|
+
nusManager?.stop()
|
|
85
93
|
val rc = nativeStop()
|
|
86
94
|
if (rc != 0) throw RuntimeException("nativeStop returned $rc")
|
|
87
95
|
true
|
|
@@ -132,12 +140,14 @@ class LxmfModule : Module() {
|
|
|
132
140
|
Function("startBLE") {
|
|
133
141
|
Log.d(TAG, "startBLE()")
|
|
134
142
|
bleManager?.start()
|
|
143
|
+
nusManager?.start()
|
|
135
144
|
true
|
|
136
145
|
}
|
|
137
146
|
|
|
138
147
|
Function("stopBLE") {
|
|
139
148
|
Log.d(TAG, "stopBLE()")
|
|
140
149
|
bleManager?.stop()
|
|
150
|
+
nusManager?.stop()
|
|
141
151
|
true
|
|
142
152
|
}
|
|
143
153
|
|
|
@@ -146,7 +156,7 @@ class LxmfModule : Module() {
|
|
|
146
156
|
}
|
|
147
157
|
|
|
148
158
|
Function("bleUnpairedRNodeCount") {
|
|
149
|
-
|
|
159
|
+
nusManager?.unpairedRNodeCount() ?: 0
|
|
150
160
|
}
|
|
151
161
|
}
|
|
152
162
|
|
|
@@ -215,6 +225,10 @@ class LxmfModule : Module() {
|
|
|
215
225
|
private external fun nativeSetLogLevel(level: Int): Int
|
|
216
226
|
private external fun nativeAbiVersion(): Int
|
|
217
227
|
|
|
228
|
+
// NUS JNI — called by NusManager (same package)
|
|
229
|
+
external fun nativeNusReceive(data: ByteArray)
|
|
230
|
+
external fun nativeNusPollTx(): ByteArray?
|
|
231
|
+
|
|
218
232
|
// BLE JNI — called by BleManager (same package)
|
|
219
233
|
// Must NOT be `internal` — Kotlin mangles internal function names in JVM bytecode,
|
|
220
234
|
// which breaks JNI symbol resolution (produces e.g. nativeBlePollTx$lxmf_react_native_debug).
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
package expo.modules.lxmf
|
|
2
|
+
|
|
3
|
+
import android.bluetooth.*
|
|
4
|
+
import android.bluetooth.le.*
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.os.Build
|
|
7
|
+
import android.os.Handler
|
|
8
|
+
import android.os.Looper
|
|
9
|
+
import android.os.ParcelUuid
|
|
10
|
+
import android.util.Log
|
|
11
|
+
import java.util.UUID
|
|
12
|
+
|
|
13
|
+
private const val NUS_TAG = "LxmfNus"
|
|
14
|
+
|
|
15
|
+
// NUS GATT UUIDs — must match nus_iface.rs constants exactly
|
|
16
|
+
val NUS_SERVICE_UUID: UUID = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e")
|
|
17
|
+
val NUS_TX_CHAR_UUID: UUID = UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e") // phone writes TO RNode
|
|
18
|
+
val NUS_RX_CHAR_UUID: UUID = UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e") // phone receives FROM RNode
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* NusManager — Android BLE client for RNode hardware (Heltec V3 etc.) via Nordic UART Service.
|
|
22
|
+
*
|
|
23
|
+
* Mirrors iOS BLEManager NUS logic:
|
|
24
|
+
* - Scans for NUS service UUID
|
|
25
|
+
* - If device is OS-paired (BOND_BONDED): connects and sets up KISS pipe to Rust NusInterface
|
|
26
|
+
* - If not paired: tracks as discoveredUnpaired (bleUnpairedRNodeCount returns this count)
|
|
27
|
+
* - RX notifications → nativeNusReceive() → Rust NusInterface (KISS deframing + transport)
|
|
28
|
+
* - TX polling → nativeNusPollTx() → write chunked to RNode's NUS TX characteristic
|
|
29
|
+
*
|
|
30
|
+
* Separate from BleManager (mesh peers) — two different scan filters, two GATT roles.
|
|
31
|
+
*/
|
|
32
|
+
class NusManager(
|
|
33
|
+
private val context: Context,
|
|
34
|
+
private val module: LxmfModule,
|
|
35
|
+
) {
|
|
36
|
+
private val bluetoothManager = context.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager
|
|
37
|
+
private val adapter: BluetoothAdapter? get() = bluetoothManager?.adapter
|
|
38
|
+
private val mainHandler = Handler(Looper.getMainLooper())
|
|
39
|
+
|
|
40
|
+
// Active GATT connections to bonded RNodes, keyed by MAC
|
|
41
|
+
private val connections = mutableMapOf<String, BluetoothGatt>()
|
|
42
|
+
// NUS TX characteristics (phone writes TO RNode), keyed by MAC
|
|
43
|
+
private val txChars = mutableMapOf<String, BluetoothGattCharacteristic>()
|
|
44
|
+
// Negotiated write MTU per connection — used for TX chunking
|
|
45
|
+
private val writeMtu = mutableMapOf<String, Int>()
|
|
46
|
+
// MACs found in scan but not OS-paired — UI should prompt user to pair in Settings
|
|
47
|
+
private val discoveredUnpaired = mutableSetOf<String>()
|
|
48
|
+
// MACs currently attempting connection
|
|
49
|
+
private val connecting = mutableSetOf<String>()
|
|
50
|
+
|
|
51
|
+
private var scanner: BluetoothLeScanner? = null
|
|
52
|
+
private var isScanning = false
|
|
53
|
+
private var isRunning = false
|
|
54
|
+
|
|
55
|
+
companion object {
|
|
56
|
+
private const val SCAN_RESTART_DELAY_MS = 30_000L
|
|
57
|
+
private const val RECONNECT_DELAY_MS = 3_000L
|
|
58
|
+
private const val DEFAULT_WRITE_MTU = 20 // conservative BLE default
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
fun start() {
|
|
64
|
+
if (isRunning) return
|
|
65
|
+
isRunning = true
|
|
66
|
+
startScanning()
|
|
67
|
+
Log.i(NUS_TAG, "NusManager started")
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fun stop() {
|
|
71
|
+
isRunning = false
|
|
72
|
+
stopScanning()
|
|
73
|
+
connections.values.forEach { it.disconnect(); it.close() }
|
|
74
|
+
connections.clear()
|
|
75
|
+
txChars.clear()
|
|
76
|
+
writeMtu.clear()
|
|
77
|
+
connecting.clear()
|
|
78
|
+
discoveredUnpaired.clear()
|
|
79
|
+
Log.i(NUS_TAG, "NusManager stopped")
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Number of RNodes visible in scan but not yet OS-paired (mirrors iOS discoveredUnpairedRNodes.count). */
|
|
83
|
+
fun unpairedRNodeCount(): Int = discoveredUnpaired.size
|
|
84
|
+
|
|
85
|
+
/** Number of fully connected and ready RNodes. */
|
|
86
|
+
fun connectedRNodeCount(): Int = connections.size
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Drain Rust NUS TX queue and write each frame to all connected RNodes.
|
|
90
|
+
* Called from LxmfModule poll runnable at the same 80ms cadence as event polling.
|
|
91
|
+
*/
|
|
92
|
+
fun pollTxAndWrite() {
|
|
93
|
+
if (connections.isEmpty()) return
|
|
94
|
+
repeat(8) {
|
|
95
|
+
val data = module.nativeNusPollTx() ?: return
|
|
96
|
+
for ((mac, char) in txChars) {
|
|
97
|
+
val gatt = connections[mac] ?: return@repeat
|
|
98
|
+
writeChunked(gatt, char, data, writeMtu[mac] ?: DEFAULT_WRITE_MTU)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private fun writeChunked(
|
|
104
|
+
gatt: BluetoothGatt,
|
|
105
|
+
char: BluetoothGattCharacteristic,
|
|
106
|
+
data: ByteArray,
|
|
107
|
+
mtu: Int,
|
|
108
|
+
) {
|
|
109
|
+
var offset = 0
|
|
110
|
+
while (offset < data.size) {
|
|
111
|
+
val end = minOf(offset + mtu, data.size)
|
|
112
|
+
val chunk = data.copyOfRange(offset, end)
|
|
113
|
+
@Suppress("DEPRECATION")
|
|
114
|
+
char.value = chunk
|
|
115
|
+
char.writeType = BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
|
|
116
|
+
@Suppress("DEPRECATION")
|
|
117
|
+
gatt.writeCharacteristic(char)
|
|
118
|
+
offset = end
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Scanning ─────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
private fun startScanning() {
|
|
125
|
+
if (isScanning) return
|
|
126
|
+
scanner = adapter?.bluetoothLeScanner ?: return
|
|
127
|
+
val filter = ScanFilter.Builder()
|
|
128
|
+
.setServiceUuid(ParcelUuid(NUS_SERVICE_UUID))
|
|
129
|
+
.build()
|
|
130
|
+
val settings = ScanSettings.Builder()
|
|
131
|
+
.setScanMode(ScanSettings.SCAN_MODE_BALANCED)
|
|
132
|
+
.build()
|
|
133
|
+
scanner?.startScan(listOf(filter), settings, scanCallback)
|
|
134
|
+
isScanning = true
|
|
135
|
+
Log.d(NUS_TAG, "NUS scan started")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private fun stopScanning() {
|
|
139
|
+
if (isScanning) {
|
|
140
|
+
scanner?.stopScan(scanCallback)
|
|
141
|
+
isScanning = false
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private val scanCallback = object : ScanCallback() {
|
|
146
|
+
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
|
147
|
+
val device = result.device ?: return
|
|
148
|
+
val mac = device.address ?: return
|
|
149
|
+
if (mac in connections || mac in connecting) return
|
|
150
|
+
|
|
151
|
+
when (device.bondState) {
|
|
152
|
+
BluetoothDevice.BOND_BONDED -> {
|
|
153
|
+
// OS-paired — safe to connect and use NUS pipe
|
|
154
|
+
discoveredUnpaired.remove(mac)
|
|
155
|
+
Log.i(NUS_TAG, "NUS: found bonded RNode $mac, connecting")
|
|
156
|
+
connecting.add(mac)
|
|
157
|
+
device.connectGatt(context, false, gattCallback, BluetoothDevice.TRANSPORT_LE)
|
|
158
|
+
}
|
|
159
|
+
else -> {
|
|
160
|
+
// Not paired — track for UI, don't auto-connect
|
|
161
|
+
if (discoveredUnpaired.add(mac)) {
|
|
162
|
+
Log.i(NUS_TAG, "NUS: found unpaired RNode $mac — pair in Bluetooth Settings first")
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
override fun onScanFailed(errorCode: Int) {
|
|
169
|
+
Log.e(NUS_TAG, "NUS scan failed: $errorCode")
|
|
170
|
+
isScanning = false
|
|
171
|
+
mainHandler.postDelayed({ startScanning() }, SCAN_RESTART_DELAY_MS)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ── GATT ─────────────────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
private val gattCallback = object : BluetoothGattCallback() {
|
|
178
|
+
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
|
|
179
|
+
val mac = gatt.device.address
|
|
180
|
+
when (newState) {
|
|
181
|
+
BluetoothProfile.STATE_CONNECTED -> {
|
|
182
|
+
Log.i(NUS_TAG, "NUS GATT connected: $mac")
|
|
183
|
+
connections[mac] = gatt
|
|
184
|
+
connecting.remove(mac)
|
|
185
|
+
discoveredUnpaired.remove(mac)
|
|
186
|
+
// Negotiate large MTU before service discovery
|
|
187
|
+
gatt.requestMtu(517)
|
|
188
|
+
}
|
|
189
|
+
BluetoothProfile.STATE_DISCONNECTED -> {
|
|
190
|
+
if (mac !in connections && mac !in connecting) return
|
|
191
|
+
Log.i(NUS_TAG, "NUS GATT disconnected: $mac (status=$status)")
|
|
192
|
+
connections.remove(mac)
|
|
193
|
+
txChars.remove(mac)
|
|
194
|
+
writeMtu.remove(mac)
|
|
195
|
+
connecting.remove(mac)
|
|
196
|
+
gatt.close()
|
|
197
|
+
// Auto-reconnect bonded devices
|
|
198
|
+
if (isRunning && gatt.device.bondState == BluetoothDevice.BOND_BONDED) {
|
|
199
|
+
mainHandler.postDelayed({
|
|
200
|
+
if (isRunning && mac !in connections && mac !in connecting) {
|
|
201
|
+
Log.i(NUS_TAG, "NUS: reconnecting $mac")
|
|
202
|
+
connecting.add(mac)
|
|
203
|
+
gatt.device.connectGatt(
|
|
204
|
+
context, false, this, BluetoothDevice.TRANSPORT_LE
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
}, RECONNECT_DELAY_MS)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) {
|
|
214
|
+
val mac = gatt.device.address
|
|
215
|
+
val effective = if (status == BluetoothGatt.GATT_SUCCESS) mtu - 3 else DEFAULT_WRITE_MTU
|
|
216
|
+
writeMtu[mac] = effective.coerceAtLeast(DEFAULT_WRITE_MTU)
|
|
217
|
+
Log.i(NUS_TAG, "NUS MTU negotiated: $mac → ${effective}B write limit")
|
|
218
|
+
gatt.discoverServices()
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
|
222
|
+
val mac = gatt.device.address
|
|
223
|
+
if (status != BluetoothGatt.GATT_SUCCESS) {
|
|
224
|
+
Log.w(NUS_TAG, "NUS service discovery failed on $mac: $status")
|
|
225
|
+
gatt.disconnect()
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
val service = gatt.getService(NUS_SERVICE_UUID)
|
|
229
|
+
if (service == null) {
|
|
230
|
+
Log.w(NUS_TAG, "NUS service not found on $mac — not an RNode?")
|
|
231
|
+
gatt.disconnect()
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// NUS TX char — phone writes KISS frames TO the RNode
|
|
236
|
+
val txChar = service.getCharacteristic(NUS_TX_CHAR_UUID)
|
|
237
|
+
if (txChar != null) txChars[mac] = txChar
|
|
238
|
+
|
|
239
|
+
// NUS RX char — subscribe for KISS frame notifications FROM the RNode
|
|
240
|
+
val rxChar = service.getCharacteristic(NUS_RX_CHAR_UUID)
|
|
241
|
+
if (rxChar != null) {
|
|
242
|
+
gatt.setCharacteristicNotification(rxChar, true)
|
|
243
|
+
val cccd = rxChar.getDescriptor(CCCD_UUID)
|
|
244
|
+
if (cccd != null) {
|
|
245
|
+
@Suppress("DEPRECATION")
|
|
246
|
+
cccd.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
|
247
|
+
@Suppress("DEPRECATION")
|
|
248
|
+
gatt.writeDescriptor(cccd)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Log.i(NUS_TAG, "NUS RNode ready: $mac (tx=${txChar != null}, rx=${rxChar != null})")
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// API < 33 compat override
|
|
256
|
+
@Suppress("OVERRIDE_DEPRECATION")
|
|
257
|
+
override fun onCharacteristicChanged(
|
|
258
|
+
gatt: BluetoothGatt,
|
|
259
|
+
characteristic: BluetoothGattCharacteristic,
|
|
260
|
+
) {
|
|
261
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) return
|
|
262
|
+
if (characteristic.uuid == NUS_RX_CHAR_UUID) {
|
|
263
|
+
@Suppress("DEPRECATION")
|
|
264
|
+
val data = characteristic.value ?: return
|
|
265
|
+
Log.d(NUS_TAG, "NUS RX(compat) ${data.size}B from ${gatt.device.address}")
|
|
266
|
+
module.nativeNusReceive(data)
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// API 33+ — value delivered directly, no stale characteristic.value
|
|
271
|
+
@androidx.annotation.RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
|
272
|
+
override fun onCharacteristicChanged(
|
|
273
|
+
gatt: BluetoothGatt,
|
|
274
|
+
characteristic: BluetoothGattCharacteristic,
|
|
275
|
+
value: ByteArray,
|
|
276
|
+
) {
|
|
277
|
+
if (characteristic.uuid == NUS_RX_CHAR_UUID) {
|
|
278
|
+
Log.d(NUS_TAG, "NUS RX ${value.size}B from ${gatt.device.address}")
|
|
279
|
+
module.nativeNusReceive(value)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -8,32 +8,32 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>liblxmf_rn.a</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-
|
|
11
|
+
<string>ios-arm64</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>liblxmf_rn.a</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
15
15
|
<array>
|
|
16
16
|
<string>arm64</string>
|
|
17
|
-
<string>x86_64</string>
|
|
18
17
|
</array>
|
|
19
18
|
<key>SupportedPlatform</key>
|
|
20
19
|
<string>ios</string>
|
|
21
|
-
<key>SupportedPlatformVariant</key>
|
|
22
|
-
<string>simulator</string>
|
|
23
20
|
</dict>
|
|
24
21
|
<dict>
|
|
25
22
|
<key>BinaryPath</key>
|
|
26
23
|
<string>liblxmf_rn.a</string>
|
|
27
24
|
<key>LibraryIdentifier</key>
|
|
28
|
-
<string>ios-
|
|
25
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
29
26
|
<key>LibraryPath</key>
|
|
30
27
|
<string>liblxmf_rn.a</string>
|
|
31
28
|
<key>SupportedArchitectures</key>
|
|
32
29
|
<array>
|
|
33
30
|
<string>arm64</string>
|
|
31
|
+
<string>x86_64</string>
|
|
34
32
|
</array>
|
|
35
33
|
<key>SupportedPlatform</key>
|
|
36
34
|
<string>ios</string>
|
|
35
|
+
<key>SupportedPlatformVariant</key>
|
|
36
|
+
<string>simulator</string>
|
|
37
37
|
</dict>
|
|
38
38
|
</array>
|
|
39
39
|
<key>CFBundlePackageType</key>
|