munim-bluetooth 0.3.2 → 0.3.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/ios/HybridMunimBluetooth.swift +165 -347
- package/package.json +1 -1
|
@@ -14,14 +14,14 @@ class HybridMunimBluetooth: HybridMunimBluetoothSpec {
|
|
|
14
14
|
// Peripheral Manager
|
|
15
15
|
private var peripheralManager: CBPeripheralManager?
|
|
16
16
|
private var peripheralServices: [CBMutableService] = []
|
|
17
|
-
private var currentAdvertisingData:
|
|
17
|
+
private var currentAdvertisingData: AdvertisingDataTypes?
|
|
18
18
|
|
|
19
19
|
// Central Manager
|
|
20
20
|
private var centralManager: CBCentralManager?
|
|
21
21
|
private var discoveredPeripherals: [String: CBPeripheral] = [:]
|
|
22
22
|
private var connectedPeripherals: [String: CBPeripheral] = [:]
|
|
23
23
|
private var peripheralCharacteristics: [String: [CBCharacteristic]] = [:]
|
|
24
|
-
private var scanOptions:
|
|
24
|
+
private var scanOptions: ScanOptions?
|
|
25
25
|
private var isScanning = false
|
|
26
26
|
|
|
27
27
|
// Event emitter
|
|
@@ -36,7 +36,7 @@ class HybridMunimBluetooth: HybridMunimBluetoothSpec {
|
|
|
36
36
|
|
|
37
37
|
// MARK: - Peripheral Features
|
|
38
38
|
|
|
39
|
-
func startAdvertising(options:
|
|
39
|
+
override func startAdvertising(options: AdvertisingOptions) throws {
|
|
40
40
|
guard let peripheralManager = peripheralManager,
|
|
41
41
|
peripheralManager.state == .poweredOn else {
|
|
42
42
|
throw NSError(domain: "MunimBluetooth", code: 1, userInfo: [NSLocalizedDescriptionKey: "Bluetooth is not powered on"])
|
|
@@ -44,31 +44,33 @@ class HybridMunimBluetooth: HybridMunimBluetoothSpec {
|
|
|
44
44
|
|
|
45
45
|
var advertisingData: [String: Any] = [:]
|
|
46
46
|
|
|
47
|
-
//
|
|
48
|
-
if
|
|
49
|
-
|
|
47
|
+
// Service UUIDs
|
|
48
|
+
if !options.serviceUUIDs.isEmpty {
|
|
49
|
+
let uuids = options.serviceUUIDs.compactMap { CBUUID(string: $0) }
|
|
50
|
+
advertisingData[CBAdvertisementDataServiceUUIDsKey] = uuids
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
//
|
|
53
|
-
if let localName = options
|
|
53
|
+
// Local name
|
|
54
|
+
if let localName = options.localName {
|
|
54
55
|
advertisingData[CBAdvertisementDataLocalNameKey] = localName
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
advertisingData[CBAdvertisementDataServiceUUIDsKey] = uuids
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if let manufacturerData = options["manufacturerData"] as? String,
|
|
58
|
+
// Manufacturer data
|
|
59
|
+
if let manufacturerData = options.manufacturerData,
|
|
63
60
|
let data = hexStringToData(manufacturerData) {
|
|
64
61
|
advertisingData[CBAdvertisementDataManufacturerDataKey] = data
|
|
65
62
|
}
|
|
66
63
|
|
|
67
|
-
|
|
64
|
+
// Advertising data
|
|
65
|
+
if let advertisingDataTypes = options.advertisingData {
|
|
66
|
+
processAdvertisingData(advertisingDataTypes, into: &advertisingData)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
currentAdvertisingData = options.advertisingData
|
|
68
70
|
peripheralManager.startAdvertising(advertisingData as? [String: Any])
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
func updateAdvertisingData(advertisingData:
|
|
73
|
+
override func updateAdvertisingData(advertisingData: AdvertisingDataTypes) throws {
|
|
72
74
|
guard let peripheralManager = peripheralManager,
|
|
73
75
|
peripheralManager.state == .poweredOn else {
|
|
74
76
|
throw NSError(domain: "MunimBluetooth", code: 1, userInfo: [NSLocalizedDescriptionKey: "Bluetooth is not powered on"])
|
|
@@ -79,360 +81,231 @@ class HybridMunimBluetooth: HybridMunimBluetoothSpec {
|
|
|
79
81
|
var newAdvertisingData: [String: Any] = [:]
|
|
80
82
|
processAdvertisingData(advertisingData, into: &newAdvertisingData)
|
|
81
83
|
|
|
82
|
-
currentAdvertisingData =
|
|
84
|
+
currentAdvertisingData = advertisingData
|
|
83
85
|
peripheralManager.startAdvertising(newAdvertisingData as? [String: Any])
|
|
84
86
|
}
|
|
85
87
|
|
|
86
|
-
func getAdvertisingData() throws ->
|
|
87
|
-
return
|
|
88
|
+
override func getAdvertisingData() throws -> Promise<AdvertisingDataTypes> {
|
|
89
|
+
return Promise { resolver in
|
|
90
|
+
resolver.resolve(self.currentAdvertisingData ?? AdvertisingDataTypes())
|
|
91
|
+
}
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
func stopAdvertising() throws {
|
|
94
|
+
override func stopAdvertising() throws {
|
|
91
95
|
peripheralManager?.stopAdvertising()
|
|
92
|
-
currentAdvertisingData =
|
|
96
|
+
currentAdvertisingData = nil
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
func setServices(services: [
|
|
99
|
+
override func setServices(services: [GATTService]) throws {
|
|
96
100
|
peripheralServices.removeAll()
|
|
97
101
|
|
|
98
|
-
for
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
let serviceUUID = CBUUID(string: uuidString)
|
|
102
|
-
let service = CBMutableService(type: serviceUUID, primary: true)
|
|
102
|
+
for service in services {
|
|
103
|
+
let serviceUUID = CBUUID(string: service.uuid)
|
|
104
|
+
let mutableService = CBMutableService(type: serviceUUID, primary: true)
|
|
103
105
|
|
|
104
106
|
var characteristics: [CBMutableCharacteristic] = []
|
|
105
107
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
default:
|
|
125
|
-
break
|
|
126
|
-
}
|
|
127
|
-
}
|
|
108
|
+
for characteristic in service.characteristics {
|
|
109
|
+
let charUUID = CBUUID(string: characteristic.uuid)
|
|
110
|
+
|
|
111
|
+
var properties: CBCharacteristicProperties = []
|
|
112
|
+
for prop in characteristic.properties {
|
|
113
|
+
switch prop {
|
|
114
|
+
case "read":
|
|
115
|
+
properties.insert(.read)
|
|
116
|
+
case "write":
|
|
117
|
+
properties.insert(.write)
|
|
118
|
+
case "writeWithoutResponse":
|
|
119
|
+
properties.insert(.writeWithoutResponse)
|
|
120
|
+
case "notify":
|
|
121
|
+
properties.insert(.notify)
|
|
122
|
+
case "indicate":
|
|
123
|
+
properties.insert(.indicate)
|
|
124
|
+
default:
|
|
125
|
+
break
|
|
128
126
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
var value: Data?
|
|
130
|
+
if let valueString = characteristic.value {
|
|
131
|
+
value = hexStringToData(valueString)
|
|
132
|
+
if !properties.contains(.read) {
|
|
133
133
|
properties.insert(.read)
|
|
134
134
|
}
|
|
135
|
-
|
|
136
|
-
let permissions: CBAttributePermissions = value != nil ? .readable : [.readable, .writeable]
|
|
137
|
-
|
|
138
|
-
let characteristic = CBMutableCharacteristic(
|
|
139
|
-
type: charUUID,
|
|
140
|
-
properties: properties,
|
|
141
|
-
value: value,
|
|
142
|
-
permissions: permissions
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
characteristics.append(characteristic)
|
|
146
135
|
}
|
|
136
|
+
|
|
137
|
+
let permissions: CBAttributePermissions = value != nil ? .readable : [.readable, .writeable]
|
|
138
|
+
|
|
139
|
+
let mutableChar = CBMutableCharacteristic(
|
|
140
|
+
type: charUUID,
|
|
141
|
+
properties: properties,
|
|
142
|
+
value: value,
|
|
143
|
+
permissions: permissions
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
characteristics.append(mutableChar)
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
peripheralServices.append(
|
|
149
|
+
mutableService.characteristics = characteristics
|
|
150
|
+
peripheralServices.append(mutableService)
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
peripheralManager?.removeAllServices()
|
|
154
153
|
for service in peripheralServices {
|
|
155
154
|
peripheralManager?.add(service)
|
|
156
155
|
}
|
|
157
156
|
}
|
|
158
157
|
|
|
159
|
-
// MARK: - Central Features
|
|
158
|
+
// MARK: - Central/Manager Features
|
|
160
159
|
|
|
161
|
-
func isBluetoothEnabled() throws -> Bool {
|
|
162
|
-
|
|
163
|
-
|
|
160
|
+
override func isBluetoothEnabled() throws -> Promise<Bool> {
|
|
161
|
+
return Promise { resolver in
|
|
162
|
+
let isEnabled = self.centralManager?.state == .poweredOn
|
|
163
|
+
resolver.resolve(isEnabled ?? false)
|
|
164
|
+
}
|
|
164
165
|
}
|
|
165
166
|
|
|
166
|
-
func requestBluetoothPermission() throws -> Bool {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
override func requestBluetoothPermission() throws -> Promise<Bool> {
|
|
168
|
+
return Promise { resolver in
|
|
169
|
+
// In iOS, permissions are handled by CBPeripheralManager/CBCentralManager
|
|
170
|
+
resolver.resolve(true)
|
|
171
|
+
}
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
func startScan(options:
|
|
174
|
+
override func startScan(options: ScanOptions?) throws {
|
|
174
175
|
guard let centralManager = centralManager,
|
|
175
176
|
centralManager.state == .poweredOn else {
|
|
176
177
|
throw NSError(domain: "MunimBluetooth", code: 1, userInfo: [NSLocalizedDescriptionKey: "Bluetooth is not powered on"])
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
guard !isScanning else { return }
|
|
180
|
-
|
|
181
180
|
scanOptions = options
|
|
182
181
|
isScanning = true
|
|
183
|
-
discoveredPeripherals.removeAll()
|
|
184
182
|
|
|
185
|
-
var
|
|
186
|
-
if let
|
|
187
|
-
|
|
183
|
+
var scanOptions: [String: Any] = [:]
|
|
184
|
+
if let options = options {
|
|
185
|
+
scanOptions[CBCentralManagerScanOptionAllowDuplicatesKey] = options.allowDuplicates ?? false
|
|
188
186
|
}
|
|
189
187
|
|
|
190
|
-
centralManager.scanForPeripherals(withServices:
|
|
191
|
-
CBCentralManagerScanOptionAllowDuplicatesKey: options?["allowDuplicates"] as? Bool ?? false
|
|
192
|
-
])
|
|
188
|
+
centralManager.scanForPeripherals(withServices: nil, options: scanOptions as [String : Any])
|
|
193
189
|
}
|
|
194
190
|
|
|
195
|
-
func stopScan() throws {
|
|
196
|
-
guard isScanning else { return }
|
|
191
|
+
override func stopScan() throws {
|
|
197
192
|
centralManager?.stopScan()
|
|
198
193
|
isScanning = false
|
|
199
|
-
scanOptions = nil
|
|
200
194
|
}
|
|
201
195
|
|
|
202
|
-
func connect(deviceId: String) throws {
|
|
203
|
-
|
|
204
|
-
|
|
196
|
+
override func connect(deviceId: String) throws -> Promise<Void> {
|
|
197
|
+
return Promise { resolver in
|
|
198
|
+
guard let peripheral = self.discoveredPeripherals[deviceId] else {
|
|
199
|
+
resolver.reject(NSError(domain: "MunimBluetooth", code: 1, userInfo: [NSLocalizedDescriptionKey: "Device not found"]))
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
self.centralManager?.connect(peripheral, options: nil)
|
|
204
|
+
self.connectedPeripherals[deviceId] = peripheral
|
|
205
|
+
resolver.resolve(())
|
|
205
206
|
}
|
|
206
|
-
|
|
207
|
-
centralManager?.connect(peripheral, options: nil)
|
|
208
207
|
}
|
|
209
208
|
|
|
210
|
-
func disconnect(deviceId: String) throws {
|
|
211
|
-
guard let peripheral = connectedPeripherals[deviceId]
|
|
212
|
-
return
|
|
213
|
-
}
|
|
214
|
-
|
|
209
|
+
override func disconnect(deviceId: String) throws {
|
|
210
|
+
guard let peripheral = connectedPeripherals[deviceId] else { return }
|
|
215
211
|
centralManager?.cancelPeripheralConnection(peripheral)
|
|
212
|
+
connectedPeripherals.removeValue(forKey: deviceId)
|
|
216
213
|
}
|
|
217
214
|
|
|
218
|
-
func discoverServices(deviceId: String) throws -> [
|
|
219
|
-
|
|
220
|
-
|
|
215
|
+
override func discoverServices(deviceId: String) throws -> Promise<[GATTService]> {
|
|
216
|
+
return Promise { resolver in
|
|
217
|
+
guard let peripheral = self.connectedPeripherals[deviceId] else {
|
|
218
|
+
resolver.reject(NSError(domain: "MunimBluetooth", code: 1, userInfo: [NSLocalizedDescriptionKey: "Device not connected"]))
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
peripheral.discoverServices(nil)
|
|
223
|
+
resolver.resolve([])
|
|
221
224
|
}
|
|
222
|
-
|
|
223
|
-
peripheral.discoverServices(nil)
|
|
224
|
-
|
|
225
|
-
// Wait for services to be discovered (in real implementation, this would be async)
|
|
226
|
-
// For now, return empty array - services will be discovered via delegate callbacks
|
|
227
|
-
return []
|
|
228
225
|
}
|
|
229
226
|
|
|
230
|
-
func readCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String) throws ->
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
guard let characteristics = peripheralCharacteristics[deviceId],
|
|
236
|
-
let characteristic = characteristics.first(where: {
|
|
237
|
-
$0.service?.uuid == CBUUID(string: serviceUUID) &&
|
|
238
|
-
$0.uuid == CBUUID(string: characteristicUUID)
|
|
239
|
-
}) else {
|
|
240
|
-
throw NSError(domain: "MunimBluetooth", code: 3, userInfo: [NSLocalizedDescriptionKey: "Characteristic not found"])
|
|
227
|
+
override func readCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String) throws -> Promise<CharacteristicValue> {
|
|
228
|
+
return Promise { resolver in
|
|
229
|
+
resolver.reject(NSError(domain: "MunimBluetooth", code: 1, userInfo: [NSLocalizedDescriptionKey: "Not implemented"]))
|
|
241
230
|
}
|
|
242
|
-
|
|
243
|
-
peripheral.readValue(for: characteristic)
|
|
244
|
-
|
|
245
|
-
// Return empty for now - value will come via delegate callback
|
|
246
|
-
return [
|
|
247
|
-
"value": "",
|
|
248
|
-
"serviceUUID": serviceUUID,
|
|
249
|
-
"characteristicUUID": characteristicUUID
|
|
250
|
-
]
|
|
251
231
|
}
|
|
252
232
|
|
|
253
|
-
func writeCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String, value: String, writeType:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
guard let characteristics = peripheralCharacteristics[deviceId],
|
|
259
|
-
let characteristic = characteristics.first(where: {
|
|
260
|
-
$0.service?.uuid == CBUUID(string: serviceUUID) &&
|
|
261
|
-
$0.uuid == CBUUID(string: characteristicUUID)
|
|
262
|
-
}) else {
|
|
263
|
-
throw NSError(domain: "MunimBluetooth", code: 3, userInfo: [NSLocalizedDescriptionKey: "Characteristic not found"])
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
guard let data = hexStringToData(value) else {
|
|
267
|
-
throw NSError(domain: "MunimBluetooth", code: 4, userInfo: [NSLocalizedDescriptionKey: "Invalid hex string"])
|
|
233
|
+
override func writeCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String, value: String, writeType: WriteType?) throws -> Promise<Void> {
|
|
234
|
+
return Promise { resolver in
|
|
235
|
+
resolver.reject(NSError(domain: "MunimBluetooth", code: 1, userInfo: [NSLocalizedDescriptionKey: "Not implemented"]))
|
|
268
236
|
}
|
|
269
|
-
|
|
270
|
-
let type: CBCharacteristicWriteType = (writeType == "writeWithoutResponse") ? .withoutResponse : .withResponse
|
|
271
|
-
peripheral.writeValue(data, for: characteristic, type: type)
|
|
272
237
|
}
|
|
273
238
|
|
|
274
|
-
func subscribeToCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String) throws {
|
|
275
|
-
|
|
276
|
-
throw NSError(domain: "MunimBluetooth", code: 2, userInfo: [NSLocalizedDescriptionKey: "Device not connected"])
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
guard let characteristics = peripheralCharacteristics[deviceId],
|
|
280
|
-
let characteristic = characteristics.first(where: {
|
|
281
|
-
$0.service?.uuid == CBUUID(string: serviceUUID) &&
|
|
282
|
-
$0.uuid == CBUUID(string: characteristicUUID)
|
|
283
|
-
}) else {
|
|
284
|
-
throw NSError(domain: "MunimBluetooth", code: 3, userInfo: [NSLocalizedDescriptionKey: "Characteristic not found"])
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
peripheral.setNotifyValue(true, for: characteristic)
|
|
239
|
+
override func subscribeToCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String) throws {
|
|
240
|
+
// Not implemented
|
|
288
241
|
}
|
|
289
242
|
|
|
290
|
-
func unsubscribeFromCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String) throws {
|
|
291
|
-
|
|
292
|
-
throw NSError(domain: "MunimBluetooth", code: 2, userInfo: [NSLocalizedDescriptionKey: "Device not connected"])
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
guard let characteristics = peripheralCharacteristics[deviceId],
|
|
296
|
-
let characteristic = characteristics.first(where: {
|
|
297
|
-
$0.service?.uuid == CBUUID(string: serviceUUID) &&
|
|
298
|
-
$0.uuid == CBUUID(string: characteristicUUID)
|
|
299
|
-
}) else {
|
|
300
|
-
throw NSError(domain: "MunimBluetooth", code: 3, userInfo: [NSLocalizedDescriptionKey: "Characteristic not found"])
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
peripheral.setNotifyValue(false, for: characteristic)
|
|
243
|
+
override func unsubscribeFromCharacteristic(deviceId: String, serviceUUID: String, characteristicUUID: String) throws {
|
|
244
|
+
// Not implemented
|
|
304
245
|
}
|
|
305
246
|
|
|
306
|
-
func getConnectedDevices() throws -> [String] {
|
|
307
|
-
return
|
|
247
|
+
override func getConnectedDevices() throws -> Promise<[String]> {
|
|
248
|
+
return Promise { resolver in
|
|
249
|
+
resolver.resolve(Array(self.connectedPeripherals.keys))
|
|
250
|
+
}
|
|
308
251
|
}
|
|
309
252
|
|
|
310
|
-
func readRSSI(deviceId: String) throws -> Double {
|
|
311
|
-
|
|
312
|
-
|
|
253
|
+
override func readRSSI(deviceId: String) throws -> Promise<Double> {
|
|
254
|
+
return Promise { resolver in
|
|
255
|
+
guard let peripheral = self.connectedPeripherals[deviceId] else {
|
|
256
|
+
resolver.reject(NSError(domain: "MunimBluetooth", code: 1, userInfo: [NSLocalizedDescriptionKey: "Device not connected"]))
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
peripheral.readRSSI()
|
|
261
|
+
resolver.resolve(0)
|
|
313
262
|
}
|
|
314
|
-
|
|
315
|
-
peripheral.readRSSI()
|
|
316
|
-
// RSSI will come via delegate callback
|
|
317
|
-
return 0.0
|
|
318
263
|
}
|
|
319
264
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
func addListener(eventName: String) throws {
|
|
323
|
-
// Event listeners are handled by the event emitter
|
|
324
|
-
// This is a no-op as Nitro modules handle events differently
|
|
265
|
+
override func addListener(eventName: String) throws {
|
|
266
|
+
// Event management
|
|
325
267
|
}
|
|
326
268
|
|
|
327
|
-
func removeListeners(count: Double) throws {
|
|
328
|
-
// Event
|
|
329
|
-
// This is a no-op as Nitro modules handle events differently
|
|
269
|
+
override func removeListeners(count: Double) throws {
|
|
270
|
+
// Event management
|
|
330
271
|
}
|
|
331
272
|
|
|
332
273
|
// MARK: - Helper Methods
|
|
333
274
|
|
|
334
|
-
private func
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
let isConnectable = (flags & 0x02) != 0
|
|
338
|
-
advertisingData[CBAdvertisementDataIsConnectable] = isConnectable
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// Service UUIDs
|
|
342
|
-
addServiceUUIDs(dataDict["incompleteServiceUUIDs16"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
343
|
-
addServiceUUIDs(dataDict["completeServiceUUIDs16"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
344
|
-
addServiceUUIDs(dataDict["incompleteServiceUUIDs32"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
345
|
-
addServiceUUIDs(dataDict["completeServiceUUIDs32"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
346
|
-
addServiceUUIDs(dataDict["incompleteServiceUUIDs128"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
347
|
-
addServiceUUIDs(dataDict["completeServiceUUIDs128"], to: &advertisingData, key: CBAdvertisementDataServiceUUIDsKey)
|
|
348
|
-
|
|
349
|
-
// Local Name
|
|
350
|
-
if let shortenedName = dataDict["shortenedLocalName"] as? String {
|
|
351
|
-
advertisingData[CBAdvertisementDataLocalNameKey] = shortenedName
|
|
352
|
-
}
|
|
353
|
-
if let completeName = dataDict["completeLocalName"] as? String {
|
|
354
|
-
advertisingData[CBAdvertisementDataLocalNameKey] = completeName
|
|
355
|
-
}
|
|
275
|
+
private func hexStringToData(_ hex: String) -> Data? {
|
|
276
|
+
var data = Data()
|
|
277
|
+
var hex = hex
|
|
356
278
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
advertisingData[CBAdvertisementDataTxPowerLevelKey] = txPower
|
|
279
|
+
if hex.count % 2 != 0 {
|
|
280
|
+
hex = "0" + hex
|
|
360
281
|
}
|
|
361
282
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
// Service Data
|
|
368
|
-
addServiceData(dataDict["serviceData16"], to: &advertisingData)
|
|
369
|
-
addServiceData(dataDict["serviceData32"], to: &advertisingData)
|
|
370
|
-
addServiceData(dataDict["serviceData128"], to: &advertisingData)
|
|
371
|
-
|
|
372
|
-
// Appearance
|
|
373
|
-
if let appearance = dataDict["appearance"] as? Int {
|
|
374
|
-
let appearanceData = Data(bytes: [UInt8(appearance & 0xFF), UInt8((appearance >> 8) & 0xFF)], count: 2)
|
|
375
|
-
advertisingData[CBUUID(string: "1800")] = appearanceData
|
|
283
|
+
let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
|
|
284
|
+
regex.enumerateMatches(in: hex, range: NSRange(hex.startIndex..., in: hex)) { match, _, _ in
|
|
285
|
+
let byteStr = (hex as NSString).substring(with: match!.range)
|
|
286
|
+
let num = UInt8(byteStr, radix: 16)!
|
|
287
|
+
data.append(num)
|
|
376
288
|
}
|
|
377
289
|
|
|
378
|
-
|
|
379
|
-
if let manufacturerData = dataDict["manufacturerData"] as? String,
|
|
380
|
-
let data = hexStringToData(manufacturerData) {
|
|
381
|
-
advertisingData[CBAdvertisementDataManufacturerDataKey] = data
|
|
382
|
-
}
|
|
290
|
+
return data.isEmpty ? nil : data
|
|
383
291
|
}
|
|
384
292
|
|
|
385
|
-
private func
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
let cbUUIDs = uuidArray.compactMap { CBUUID(string: $0) }
|
|
389
|
-
if !cbUUIDs.isEmpty {
|
|
390
|
-
if var existing = advertisingData[key] as? [CBUUID] {
|
|
391
|
-
existing.append(contentsOf: cbUUIDs)
|
|
392
|
-
advertisingData[key] = existing
|
|
393
|
-
} else {
|
|
394
|
-
advertisingData[key] = cbUUIDs
|
|
395
|
-
}
|
|
293
|
+
private func processAdvertisingData(_ data: AdvertisingDataTypes, into advertisingData: inout [String: Any]) {
|
|
294
|
+
if let flags = data.flags {
|
|
295
|
+
advertisingData[CBAdvertisementDataIsConnectable] = true
|
|
396
296
|
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
private func addServiceData(_ serviceDataArray: Any?, to advertisingData: inout [String: Any]) {
|
|
400
|
-
guard let array = serviceDataArray as? [[String: Any]] else { return }
|
|
401
297
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
let dataString = serviceData["data"] as? String,
|
|
405
|
-
let data = hexStringToData(dataString) else { continue }
|
|
406
|
-
|
|
407
|
-
advertisingData[uuid] = data
|
|
298
|
+
if let completeLocalName = data.completeLocalName {
|
|
299
|
+
advertisingData[CBAdvertisementDataLocalNameKey] = completeLocalName
|
|
408
300
|
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
private func hexStringToData(_ hexString: String) -> Data? {
|
|
412
|
-
let cleanHex = hexString.replacingOccurrences(of: " ", with: "")
|
|
413
|
-
guard cleanHex.count % 2 == 0 else { return nil }
|
|
414
301
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
while index < cleanHex.endIndex {
|
|
419
|
-
let nextIndex = cleanHex.index(index, offsetBy: 2)
|
|
420
|
-
guard nextIndex <= cleanHex.endIndex else { break }
|
|
421
|
-
|
|
422
|
-
let byteString = String(cleanHex[index..<nextIndex])
|
|
423
|
-
if let byte = UInt8(byteString, radix: 16) {
|
|
424
|
-
data.append(byte)
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
index = nextIndex
|
|
302
|
+
if let txPowerLevel = data.txPowerLevel {
|
|
303
|
+
advertisingData[CBAdvertisementDataTxPowerLevelKey] = txPowerLevel
|
|
428
304
|
}
|
|
429
|
-
|
|
430
|
-
return data
|
|
431
305
|
}
|
|
432
306
|
}
|
|
433
307
|
|
|
434
308
|
// MARK: - CBPeripheralManagerDelegate
|
|
435
|
-
|
|
436
309
|
extension HybridMunimBluetooth: CBPeripheralManagerDelegate {
|
|
437
310
|
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
|
|
438
311
|
// Handle state updates
|
|
@@ -440,34 +313,36 @@ extension HybridMunimBluetooth: CBPeripheralManagerDelegate {
|
|
|
440
313
|
|
|
441
314
|
func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) {
|
|
442
315
|
if let error = error {
|
|
443
|
-
|
|
316
|
+
eventEmitter?.emit("advertisingError", ["error": error.localizedDescription])
|
|
317
|
+
} else {
|
|
318
|
+
eventEmitter?.emit("advertisingStarted", [:])
|
|
444
319
|
}
|
|
445
320
|
}
|
|
446
321
|
|
|
447
322
|
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
|
|
448
323
|
if let error = error {
|
|
449
|
-
|
|
324
|
+
eventEmitter?.emit("serviceError", ["error": error.localizedDescription])
|
|
325
|
+
} else {
|
|
326
|
+
eventEmitter?.emit("serviceAdded", ["uuid": service.uuid.uuidString])
|
|
450
327
|
}
|
|
451
328
|
}
|
|
452
329
|
}
|
|
453
330
|
|
|
454
331
|
// MARK: - CBCentralManagerDelegate
|
|
455
|
-
|
|
456
332
|
extension HybridMunimBluetooth: CBCentralManagerDelegate {
|
|
457
333
|
func centralManagerDidUpdateState(_ central: CBCentralManager) {
|
|
458
|
-
|
|
334
|
+
let state = central.state
|
|
335
|
+
eventEmitter?.emit("bluetoothStateChanged", ["state": state.rawValue])
|
|
459
336
|
}
|
|
460
337
|
|
|
461
338
|
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String: Any], rssi RSSI: NSNumber) {
|
|
462
339
|
let deviceId = peripheral.identifier.uuidString
|
|
463
340
|
discoveredPeripherals[deviceId] = peripheral
|
|
464
341
|
|
|
465
|
-
// Emit deviceFound event
|
|
466
342
|
eventEmitter?.emit("deviceFound", [
|
|
467
343
|
"id": deviceId,
|
|
468
344
|
"name": peripheral.name ?? "",
|
|
469
|
-
"rssi": RSSI.intValue
|
|
470
|
-
"advertisingData": advertisementData
|
|
345
|
+
"rssi": RSSI.intValue
|
|
471
346
|
])
|
|
472
347
|
}
|
|
473
348
|
|
|
@@ -476,7 +351,6 @@ extension HybridMunimBluetooth: CBCentralManagerDelegate {
|
|
|
476
351
|
connectedPeripherals[deviceId] = peripheral
|
|
477
352
|
peripheral.delegate = self
|
|
478
353
|
|
|
479
|
-
// Emit deviceConnected event
|
|
480
354
|
eventEmitter?.emit("deviceConnected", ["id": deviceId])
|
|
481
355
|
}
|
|
482
356
|
|
|
@@ -485,14 +359,11 @@ extension HybridMunimBluetooth: CBCentralManagerDelegate {
|
|
|
485
359
|
connectedPeripherals.removeValue(forKey: deviceId)
|
|
486
360
|
peripheralCharacteristics.removeValue(forKey: deviceId)
|
|
487
361
|
|
|
488
|
-
// Emit deviceDisconnected event
|
|
489
362
|
eventEmitter?.emit("deviceDisconnected", ["id": deviceId])
|
|
490
363
|
}
|
|
491
364
|
|
|
492
365
|
func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
|
|
493
366
|
let deviceId = peripheral.identifier.uuidString
|
|
494
|
-
|
|
495
|
-
// Emit connectionFailed event
|
|
496
367
|
eventEmitter?.emit("connectionFailed", [
|
|
497
368
|
"id": deviceId,
|
|
498
369
|
"error": error?.localizedDescription ?? "Unknown error"
|
|
@@ -501,24 +372,17 @@ extension HybridMunimBluetooth: CBCentralManagerDelegate {
|
|
|
501
372
|
}
|
|
502
373
|
|
|
503
374
|
// MARK: - CBPeripheralDelegate
|
|
504
|
-
|
|
505
375
|
extension HybridMunimBluetooth: CBPeripheralDelegate {
|
|
506
376
|
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
|
|
507
377
|
let deviceId = peripheral.identifier.uuidString
|
|
508
378
|
|
|
509
379
|
guard let services = peripheral.services else { return }
|
|
510
380
|
|
|
511
|
-
var servicesArray: [[String: Any]] = []
|
|
512
381
|
for service in services {
|
|
513
382
|
peripheral.discoverCharacteristics(nil, for: service)
|
|
514
|
-
servicesArray.append(["uuid": service.uuid.uuidString])
|
|
515
383
|
}
|
|
516
384
|
|
|
517
|
-
|
|
518
|
-
eventEmitter?.emit("servicesDiscovered", [
|
|
519
|
-
"id": deviceId,
|
|
520
|
-
"services": servicesArray
|
|
521
|
-
])
|
|
385
|
+
eventEmitter?.emit("servicesDiscovered", ["id": deviceId])
|
|
522
386
|
}
|
|
523
387
|
|
|
524
388
|
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
|
|
@@ -531,26 +395,7 @@ extension HybridMunimBluetooth: CBPeripheralDelegate {
|
|
|
531
395
|
}
|
|
532
396
|
peripheralCharacteristics[deviceId]?.append(contentsOf: characteristics)
|
|
533
397
|
|
|
534
|
-
|
|
535
|
-
var characteristicsArray: [[String: Any]] = []
|
|
536
|
-
for characteristic in characteristics {
|
|
537
|
-
var properties: [String] = []
|
|
538
|
-
if characteristic.properties.contains(.read) { properties.append("read") }
|
|
539
|
-
if characteristic.properties.contains(.write) { properties.append("write") }
|
|
540
|
-
if characteristic.properties.contains(.notify) { properties.append("notify") }
|
|
541
|
-
if characteristic.properties.contains(.indicate) { properties.append("indicate") }
|
|
542
|
-
|
|
543
|
-
characteristicsArray.append([
|
|
544
|
-
"uuid": characteristic.uuid.uuidString,
|
|
545
|
-
"properties": properties
|
|
546
|
-
])
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
eventEmitter?.emit("characteristicsDiscovered", [
|
|
550
|
-
"id": deviceId,
|
|
551
|
-
"serviceUUID": service.uuid.uuidString,
|
|
552
|
-
"characteristics": characteristicsArray
|
|
553
|
-
])
|
|
398
|
+
eventEmitter?.emit("characteristicsDiscovered", ["id": deviceId])
|
|
554
399
|
}
|
|
555
400
|
|
|
556
401
|
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
|
|
@@ -560,7 +405,6 @@ extension HybridMunimBluetooth: CBPeripheralDelegate {
|
|
|
560
405
|
|
|
561
406
|
let hexString = data.map { String(format: "%02x", $0) }.joined()
|
|
562
407
|
|
|
563
|
-
// Emit characteristicValueChanged event
|
|
564
408
|
eventEmitter?.emit("characteristicValueChanged", [
|
|
565
409
|
"id": deviceId,
|
|
566
410
|
"serviceUUID": characteristic.service?.uuid.uuidString ?? "",
|
|
@@ -575,29 +419,10 @@ extension HybridMunimBluetooth: CBPeripheralDelegate {
|
|
|
575
419
|
if let error = error {
|
|
576
420
|
eventEmitter?.emit("writeError", [
|
|
577
421
|
"id": deviceId,
|
|
578
|
-
"serviceUUID": characteristic.service?.uuid.uuidString ?? "",
|
|
579
|
-
"characteristicUUID": characteristic.uuid.uuidString,
|
|
580
422
|
"error": error.localizedDescription
|
|
581
423
|
])
|
|
582
424
|
} else {
|
|
583
|
-
eventEmitter?.emit("writeSuccess", [
|
|
584
|
-
"id": deviceId,
|
|
585
|
-
"serviceUUID": characteristic.service?.uuid.uuidString ?? "",
|
|
586
|
-
"characteristicUUID": characteristic.uuid.uuidString
|
|
587
|
-
])
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
|
|
592
|
-
let deviceId = peripheral.identifier.uuidString
|
|
593
|
-
|
|
594
|
-
if let error = error {
|
|
595
|
-
eventEmitter?.emit("subscriptionError", [
|
|
596
|
-
"id": deviceId,
|
|
597
|
-
"serviceUUID": characteristic.service?.uuid.uuidString ?? "",
|
|
598
|
-
"characteristicUUID": characteristic.uuid.uuidString,
|
|
599
|
-
"error": error.localizedDescription
|
|
600
|
-
])
|
|
425
|
+
eventEmitter?.emit("writeSuccess", ["id": deviceId])
|
|
601
426
|
}
|
|
602
427
|
}
|
|
603
428
|
|
|
@@ -605,21 +430,14 @@ extension HybridMunimBluetooth: CBPeripheralDelegate {
|
|
|
605
430
|
let deviceId = peripheral.identifier.uuidString
|
|
606
431
|
|
|
607
432
|
if let error = error {
|
|
608
|
-
eventEmitter?.emit("rssiError", [
|
|
609
|
-
"id": deviceId,
|
|
610
|
-
"error": error.localizedDescription
|
|
611
|
-
])
|
|
433
|
+
eventEmitter?.emit("rssiError", ["error": error.localizedDescription])
|
|
612
434
|
} else {
|
|
613
|
-
eventEmitter?.emit("rssiUpdated", [
|
|
614
|
-
"id": deviceId,
|
|
615
|
-
"rssi": RSSI.intValue
|
|
616
|
-
])
|
|
435
|
+
eventEmitter?.emit("rssiUpdated", ["id": deviceId, "rssi": RSSI.intValue])
|
|
617
436
|
}
|
|
618
437
|
}
|
|
619
438
|
}
|
|
620
439
|
|
|
621
|
-
// MARK: -
|
|
622
|
-
|
|
440
|
+
// MARK: - Helper Classes
|
|
623
441
|
class NitroEventEmitter {
|
|
624
442
|
private let moduleName: String
|
|
625
443
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "munim-bluetooth",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.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",
|