@capacitor-community/bluetooth-le 7.0.0 → 7.1.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.
Files changed (55) hide show
  1. package/CapacitorCommunityBluetoothLe.podspec +17 -17
  2. package/LICENSE +21 -21
  3. package/README.md +95 -26
  4. package/android/build.gradle +68 -68
  5. package/android/src/main/AndroidManifest.xml +22 -22
  6. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BluetoothLe.kt +1061 -1009
  7. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Conversion.kt +51 -51
  8. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Device.kt +720 -718
  9. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceList.kt +28 -28
  10. package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt +189 -189
  11. package/dist/docs.json +892 -78
  12. package/dist/esm/bleClient.d.ts +277 -277
  13. package/dist/esm/bleClient.js +349 -349
  14. package/dist/esm/bleClient.js.map +1 -1
  15. package/dist/esm/config.d.ts +53 -53
  16. package/dist/esm/config.js +2 -2
  17. package/dist/esm/config.js.map +1 -1
  18. package/dist/esm/conversion.d.ts +34 -34
  19. package/dist/esm/conversion.js +84 -84
  20. package/dist/esm/conversion.js.map +1 -1
  21. package/dist/esm/definitions.d.ts +301 -292
  22. package/dist/esm/definitions.js +42 -42
  23. package/dist/esm/definitions.js.map +1 -1
  24. package/dist/esm/index.d.ts +5 -5
  25. package/dist/esm/index.js +5 -5
  26. package/dist/esm/index.js.map +1 -1
  27. package/dist/esm/plugin.d.ts +2 -2
  28. package/dist/esm/plugin.js +4 -4
  29. package/dist/esm/plugin.js.map +1 -1
  30. package/dist/esm/queue.d.ts +3 -3
  31. package/dist/esm/queue.js +17 -17
  32. package/dist/esm/queue.js.map +1 -1
  33. package/dist/esm/timeout.d.ts +1 -1
  34. package/dist/esm/timeout.js +9 -9
  35. package/dist/esm/timeout.js.map +1 -1
  36. package/dist/esm/validators.d.ts +1 -1
  37. package/dist/esm/validators.js +11 -11
  38. package/dist/esm/validators.js.map +1 -1
  39. package/dist/esm/web.d.ts +56 -56
  40. package/dist/esm/web.js +340 -335
  41. package/dist/esm/web.js.map +1 -1
  42. package/dist/plugin.cjs.js +841 -834
  43. package/dist/plugin.cjs.js.map +1 -1
  44. package/dist/plugin.js +841 -834
  45. package/dist/plugin.js.map +1 -1
  46. package/ios/Plugin/Conversion.swift +83 -83
  47. package/ios/Plugin/Device.swift +423 -423
  48. package/ios/Plugin/DeviceManager.swift +401 -349
  49. package/ios/Plugin/Info.plist +24 -24
  50. package/ios/Plugin/Logging.swift +8 -8
  51. package/ios/Plugin/Plugin.h +10 -10
  52. package/ios/Plugin/Plugin.m +41 -41
  53. package/ios/Plugin/Plugin.swift +682 -636
  54. package/ios/Plugin/ThreadSafeDictionary.swift +13 -13
  55. package/package.json +101 -101
@@ -1,636 +1,682 @@
1
- // swiftlint:disable identifier_name
2
- // swiftlint:disable type_body_length
3
- import Foundation
4
- import Capacitor
5
- import CoreBluetooth
6
-
7
- let CONNECTION_TIMEOUT: Double = 10
8
- let DEFAULT_TIMEOUT: Double = 5
9
-
10
- @objc(BluetoothLe)
11
- public class BluetoothLe: CAPPlugin {
12
- typealias BleDevice = [String: Any]
13
- typealias BleService = [String: Any]
14
- typealias BleCharacteristic = [String: Any]
15
- typealias BleDescriptor = [String: Any]
16
- private var deviceManager: DeviceManager?
17
- private var deviceMap = [String: Device]()
18
- private var displayStrings = [String: String]()
19
-
20
- override public func load() {
21
- self.displayStrings = self.getDisplayStrings()
22
- }
23
-
24
- @objc func initialize(_ call: CAPPluginCall) {
25
- self.deviceManager = DeviceManager(self.bridge?.viewController, self.displayStrings, {(success, message) in
26
- if success {
27
- call.resolve()
28
- } else {
29
- call.reject(message)
30
- }
31
- })
32
- }
33
-
34
- @objc func isEnabled(_ call: CAPPluginCall) {
35
- guard let deviceManager = self.getDeviceManager(call) else { return }
36
- let enabled: Bool = deviceManager.isEnabled()
37
- call.resolve(["value": enabled])
38
- }
39
-
40
- @objc func requestEnable(_ call: CAPPluginCall) {
41
- call.unavailable("requestEnable is not available on iOS.")
42
- }
43
-
44
- @objc func enable(_ call: CAPPluginCall) {
45
- call.unavailable("enable is not available on iOS.")
46
- }
47
-
48
- @objc func disable(_ call: CAPPluginCall) {
49
- call.unavailable("disable is not available on iOS.")
50
- }
51
-
52
- @objc func startEnabledNotifications(_ call: CAPPluginCall) {
53
- guard let deviceManager = self.getDeviceManager(call) else { return }
54
- deviceManager.registerStateReceiver({(enabled) in
55
- self.notifyListeners("onEnabledChanged", data: ["value": enabled])
56
- })
57
- call.resolve()
58
- }
59
-
60
- @objc func stopEnabledNotifications(_ call: CAPPluginCall) {
61
- guard let deviceManager = self.getDeviceManager(call) else { return }
62
- deviceManager.unregisterStateReceiver()
63
- call.resolve()
64
- }
65
-
66
- @objc func isLocationEnabled(_ call: CAPPluginCall) {
67
- call.unavailable("isLocationEnabled is not available on iOS.")
68
- }
69
-
70
- @objc func openLocationSettings(_ call: CAPPluginCall) {
71
- call.unavailable("openLocationSettings is not available on iOS.")
72
- }
73
-
74
- @objc func openBluetoothSettings(_ call: CAPPluginCall) {
75
- call.unavailable("openBluetoothSettings is not available on iOS.")
76
- }
77
-
78
- @objc func openAppSettings(_ call: CAPPluginCall) {
79
- guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
80
- call.reject("Cannot open app settings.")
81
- return
82
- }
83
-
84
- DispatchQueue.main.async {
85
- if UIApplication.shared.canOpenURL(settingsUrl) {
86
- UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
87
- call.resolve([
88
- "value": success
89
- ])
90
- })
91
- } else {
92
- call.reject("Cannot open app settings.")
93
- }
94
- }
95
- }
96
-
97
- @objc func setDisplayStrings(_ call: CAPPluginCall) {
98
- for key in ["noDeviceFound", "availableDevices", "scanning", "cancel"] {
99
- if call.getString(key) != nil {
100
- self.displayStrings[key] = call.getString(key)
101
- }
102
- }
103
- call.resolve()
104
- }
105
-
106
- @objc func requestDevice(_ call: CAPPluginCall) {
107
- guard let deviceManager = self.getDeviceManager(call) else { return }
108
- deviceManager.setDisplayStrings(self.displayStrings)
109
-
110
- let serviceUUIDs = self.getServiceUUIDs(call)
111
- let name = call.getString("name")
112
- let namePrefix = call.getString("namePrefix")
113
-
114
- deviceManager.startScanning(
115
- serviceUUIDs,
116
- name,
117
- namePrefix,
118
- false,
119
- true,
120
- 30, {(success, message) in
121
- // selected a device
122
- if success {
123
- guard let device = deviceManager.getDevice(message) else {
124
- call.reject("Device not found.")
125
- return
126
- }
127
- self.deviceMap[device.getId()] = device
128
- let bleDevice: BleDevice = self.getBleDevice(device)
129
- call.resolve(bleDevice)
130
- } else {
131
- call.reject(message)
132
- }
133
- }, {(_, _, _) in
134
-
135
- }
136
- )
137
- }
138
-
139
- @objc func requestLEScan(_ call: CAPPluginCall) {
140
- guard let deviceManager = self.getDeviceManager(call) else { return }
141
-
142
- let serviceUUIDs = self.getServiceUUIDs(call)
143
- let name = call.getString("name")
144
- let namePrefix = call.getString("namePrefix")
145
- let allowDuplicates = call.getBool("allowDuplicates", false)
146
-
147
- deviceManager.startScanning(
148
- serviceUUIDs,
149
- name,
150
- namePrefix,
151
- allowDuplicates,
152
- false,
153
- nil, {(success, message) in
154
- if success {
155
- call.resolve()
156
- } else {
157
- call.reject(message)
158
- }
159
- }, {(device, advertisementData, rssi) in
160
- self.deviceMap[device.getId()] = device
161
- let data = self.getScanResult(device, advertisementData, rssi)
162
- self.notifyListeners("onScanResult", data: data)
163
- }
164
- )
165
- }
166
-
167
- @objc func stopLEScan(_ call: CAPPluginCall) {
168
- guard let deviceManager = self.getDeviceManager(call) else { return }
169
- deviceManager.stopScan()
170
- call.resolve()
171
- }
172
-
173
- @objc func getDevices(_ call: CAPPluginCall) {
174
- guard let deviceManager = self.getDeviceManager(call) else { return }
175
- guard let deviceIds = call.getArray("deviceIds", String.self) else {
176
- call.reject("deviceIds must be provided")
177
- return
178
- }
179
- let deviceUUIDs: [UUID] = deviceIds.compactMap({ deviceId in
180
- return UUID(uuidString: deviceId)
181
- })
182
- let peripherals = deviceManager.getDevices(deviceUUIDs)
183
- let bleDevices: [BleDevice] = peripherals.map({peripheral in
184
- let deviceId = peripheral.identifier.uuidString
185
- guard let device = self.deviceMap[deviceId] else {
186
- let newDevice = Device(peripheral)
187
- self.deviceMap[newDevice.getId()] = newDevice
188
- return self.getBleDevice(newDevice)
189
- }
190
- return self.getBleDevice(device)
191
- })
192
- call.resolve(["devices": bleDevices])
193
- }
194
-
195
- @objc func getConnectedDevices(_ call: CAPPluginCall) {
196
- guard let deviceManager = self.getDeviceManager(call) else { return }
197
- guard let services = call.getArray("services", String.self) else {
198
- call.reject("services must be provided")
199
- return
200
- }
201
- let serviceUUIDs: [CBUUID] = services.compactMap({ service in
202
- return CBUUID(string: service)
203
- })
204
- let peripherals = deviceManager.getConnectedDevices(serviceUUIDs)
205
- let bleDevices: [BleDevice] = peripherals.map({peripheral in
206
- let deviceId = peripheral.identifier.uuidString
207
- guard let device = self.deviceMap[deviceId] else {
208
- let newDevice = Device(peripheral)
209
- self.deviceMap[newDevice.getId()] = newDevice
210
- return self.getBleDevice(newDevice)
211
- }
212
- return self.getBleDevice(device)
213
- })
214
- call.resolve(["devices": bleDevices])
215
- }
216
-
217
- @objc func connect(_ call: CAPPluginCall) {
218
- guard self.getDeviceManager(call) != nil else { return }
219
- guard let device = self.getDevice(call, checkConnection: false) else { return }
220
- let timeout = self.getTimeout(call, defaultTimeout: CONNECTION_TIMEOUT)
221
- device.setOnConnected(timeout, {(success, message) in
222
- if success {
223
- // only resolve after service discovery
224
- call.resolve()
225
- } else {
226
- call.reject(message)
227
- }
228
- })
229
- self.deviceManager?.setOnDisconnected(device, {(_, _) in
230
- let key = "disconnected|\(device.getId())"
231
- self.notifyListeners(key, data: nil)
232
- })
233
- self.deviceManager?.connect(device, timeout, {(success, message) in
234
- if success {
235
- log("Connected to peripheral. Waiting for service discovery.")
236
- } else {
237
- call.reject(message)
238
- }
239
- })
240
-
241
- }
242
-
243
- @objc func createBond(_ call: CAPPluginCall) {
244
- call.unavailable("createBond is not available on iOS.")
245
- }
246
-
247
- @objc func isBonded(_ call: CAPPluginCall) {
248
- call.unavailable("isBonded is not available on iOS.")
249
- }
250
-
251
- @objc func getBondedDevices(_ call: CAPPluginCall) {
252
- call.unavailable("getBondedDevices is not available on iOS.")
253
- }
254
-
255
- @objc func disconnect(_ call: CAPPluginCall) {
256
- guard self.getDeviceManager(call) != nil else { return }
257
- guard let device = self.getDevice(call, checkConnection: false) else { return }
258
- let timeout = self.getTimeout(call)
259
- self.deviceManager?.disconnect(device, timeout, {(success, message) in
260
- if success {
261
- call.resolve()
262
- } else {
263
- call.reject(message)
264
- }
265
- })
266
- }
267
-
268
- @objc func getServices(_ call: CAPPluginCall) {
269
- guard self.getDeviceManager(call) != nil else { return }
270
- guard let device = self.getDevice(call) else { return }
271
- let services = device.getServices()
272
- var bleServices = [BleService]()
273
- for service in services {
274
- var bleCharacteristics = [BleCharacteristic]()
275
- for characteristic in service.characteristics ?? [] {
276
- var bleDescriptors = [BleDescriptor]()
277
- for descriptor in characteristic.descriptors ?? [] {
278
- bleDescriptors.append([
279
- "uuid": cbuuidToString(descriptor.uuid)
280
- ])
281
- }
282
- bleCharacteristics.append([
283
- "uuid": cbuuidToString(characteristic.uuid),
284
- "properties": getProperties(characteristic),
285
- "descriptors": bleDescriptors
286
- ])
287
- }
288
- bleServices.append([
289
- "uuid": cbuuidToString(service.uuid),
290
- "characteristics": bleCharacteristics
291
- ])
292
- }
293
- call.resolve(["services": bleServices])
294
- }
295
-
296
- private func getProperties(_ characteristic: CBCharacteristic) -> [String: Bool] {
297
- return [
298
- "broadcast": characteristic.properties.contains(CBCharacteristicProperties.broadcast),
299
- "read": characteristic.properties.contains(CBCharacteristicProperties.read),
300
- "writeWithoutResponse": characteristic.properties.contains(CBCharacteristicProperties.writeWithoutResponse),
301
- "write": characteristic.properties.contains(CBCharacteristicProperties.write),
302
- "notify": characteristic.properties.contains(CBCharacteristicProperties.notify),
303
- "indicate": characteristic.properties.contains(CBCharacteristicProperties.indicate),
304
- "authenticatedSignedWrites": characteristic.properties.contains(CBCharacteristicProperties.authenticatedSignedWrites),
305
- "extendedProperties": characteristic.properties.contains(CBCharacteristicProperties.extendedProperties),
306
- "notifyEncryptionRequired": characteristic.properties.contains(CBCharacteristicProperties.notifyEncryptionRequired),
307
- "indicateEncryptionRequired": characteristic.properties.contains(CBCharacteristicProperties.indicateEncryptionRequired)
308
- ]
309
- }
310
-
311
- @objc func discoverServices(_ call: CAPPluginCall) {
312
- guard self.getDeviceManager(call) != nil else { return }
313
- guard let device = self.getDevice(call) else { return }
314
- let timeout = self.getTimeout(call)
315
- device.discoverServices(timeout, {(success, value) in
316
- if success {
317
- call.resolve()
318
- } else {
319
- call.reject(value)
320
- }
321
- })
322
- }
323
-
324
- @objc func getMtu(_ call: CAPPluginCall) {
325
- guard self.getDeviceManager(call) != nil else { return }
326
- guard let device = self.getDevice(call) else { return }
327
- call.resolve([
328
- "value": device.getMtu()
329
- ])
330
- }
331
-
332
- @objc func requestConnectionPriority(_ call: CAPPluginCall) {
333
- call.unavailable("requestConnectionPriority is not available on iOS.")
334
- }
335
-
336
- @objc func readRssi(_ call: CAPPluginCall) {
337
- guard self.getDeviceManager(call) != nil else { return }
338
- guard let device = self.getDevice(call) else { return }
339
- let timeout = self.getTimeout(call)
340
- device.readRssi(timeout, {(success, value) in
341
- if success {
342
- call.resolve([
343
- "value": value
344
- ])
345
- } else {
346
- call.reject(value)
347
- }
348
- })
349
- }
350
-
351
- @objc func read(_ call: CAPPluginCall) {
352
- guard self.getDeviceManager(call) != nil else { return }
353
- guard let device = self.getDevice(call) else { return }
354
- guard let characteristic = self.getCharacteristic(call) else { return }
355
- let timeout = self.getTimeout(call)
356
- device.read(characteristic.0, characteristic.1, timeout, {(success, value) in
357
- if success {
358
- call.resolve([
359
- "value": value
360
- ])
361
- } else {
362
- call.reject(value)
363
- }
364
- })
365
- }
366
-
367
- @objc func write(_ call: CAPPluginCall) {
368
- guard self.getDeviceManager(call) != nil else { return }
369
- guard let device = self.getDevice(call) else { return }
370
- guard let characteristic = self.getCharacteristic(call) else { return }
371
- guard let value = call.getString("value") else {
372
- call.reject("value must be provided")
373
- return
374
- }
375
- let writeType = CBCharacteristicWriteType.withResponse
376
- let timeout = self.getTimeout(call)
377
- device.write(
378
- characteristic.0,
379
- characteristic.1,
380
- value,
381
- writeType,
382
- timeout, {(success, value) in
383
- if success {
384
- call.resolve()
385
- } else {
386
- call.reject(value)
387
- }
388
- })
389
- }
390
-
391
- @objc func writeWithoutResponse(_ call: CAPPluginCall) {
392
- guard self.getDeviceManager(call) != nil else { return }
393
- guard let device = self.getDevice(call) else { return }
394
- guard let characteristic = self.getCharacteristic(call) else { return }
395
- guard let value = call.getString("value") else {
396
- call.reject("value must be provided")
397
- return
398
- }
399
- let writeType = CBCharacteristicWriteType.withoutResponse
400
- let timeout = self.getTimeout(call)
401
- device.write(
402
- characteristic.0,
403
- characteristic.1,
404
- value,
405
- writeType,
406
- timeout, {(success, value) in
407
- if success {
408
- call.resolve()
409
- } else {
410
- call.reject(value)
411
- }
412
- })
413
- }
414
-
415
- @objc func readDescriptor(_ call: CAPPluginCall) {
416
- guard self.getDeviceManager(call) != nil else { return }
417
- guard let device = self.getDevice(call) else { return }
418
- guard let descriptor = self.getDescriptor(call) else { return }
419
- let timeout = self.getTimeout(call)
420
- device.readDescriptor(
421
- descriptor.0,
422
- descriptor.1,
423
- descriptor.2,
424
- timeout, {(success, value) in
425
- if success {
426
- call.resolve([
427
- "value": value
428
- ])
429
- } else {
430
- call.reject(value)
431
- }
432
- })
433
- }
434
-
435
- @objc func writeDescriptor(_ call: CAPPluginCall) {
436
- guard self.getDeviceManager(call) != nil else { return }
437
- guard let device = self.getDevice(call) else { return }
438
- guard let descriptor = self.getDescriptor(call) else { return }
439
- guard let value = call.getString("value") else {
440
- call.reject("value must be provided")
441
- return
442
- }
443
- let timeout = self.getTimeout(call)
444
- device.writeDescriptor(
445
- descriptor.0,
446
- descriptor.1,
447
- descriptor.2,
448
- value,
449
- timeout, {(success, value) in
450
- if success {
451
- call.resolve()
452
- } else {
453
- call.reject(value)
454
- }
455
- })
456
- }
457
-
458
- @objc func startNotifications(_ call: CAPPluginCall) {
459
- guard self.getDeviceManager(call) != nil else { return }
460
- guard let device = self.getDevice(call) else { return }
461
- guard let characteristic = self.getCharacteristic(call) else { return }
462
- let timeout = self.getTimeout(call)
463
- device.setNotifications(
464
- characteristic.0,
465
- characteristic.1,
466
- true, {(_, value) in
467
- let key = "notification|\(device.getId())|\(characteristic.0.uuidString.lowercased())|\(characteristic.1.uuidString.lowercased())"
468
- self.notifyListeners(key, data: ["value": value])
469
- },
470
- timeout, {(success, value) in
471
- if success {
472
- call.resolve()
473
- } else {
474
- call.reject(value)
475
- }
476
- })
477
- }
478
-
479
- @objc func stopNotifications(_ call: CAPPluginCall) {
480
- guard self.getDeviceManager(call) != nil else { return }
481
- guard let device = self.getDevice(call) else { return }
482
- guard let characteristic = self.getCharacteristic(call) else { return }
483
- let timeout = self.getTimeout(call)
484
- device.setNotifications(
485
- characteristic.0,
486
- characteristic.1,
487
- false,
488
- nil,
489
- timeout, {(success, value) in
490
- if success {
491
- call.resolve()
492
- } else {
493
- call.reject(value)
494
- }
495
- })
496
- }
497
-
498
- private func getDisplayStrings() -> [String: String] {
499
- let configDisplayStrings = getConfig().getObject("displayStrings") as? [String: String] ?? [String: String]()
500
- var displayStrings = [String: String]()
501
- displayStrings["noDeviceFound"] = configDisplayStrings["noDeviceFound"] ?? "No device found"
502
- displayStrings["availableDevices"] = configDisplayStrings["availableDevices"] ?? "Available devices"
503
- displayStrings["scanning"] = configDisplayStrings["scanning"] ?? "Scanning..."
504
- displayStrings["cancel"] = configDisplayStrings["cancel"] ?? "Cancel"
505
- return displayStrings
506
- }
507
-
508
- private func getDeviceManager(_ call: CAPPluginCall) -> DeviceManager? {
509
- guard let deviceManager = self.deviceManager else {
510
- call.reject("Bluetooth LE not initialized.")
511
- return nil
512
- }
513
- return deviceManager
514
- }
515
-
516
- private func getServiceUUIDs(_ call: CAPPluginCall) -> [CBUUID] {
517
- let services = call.getArray("services", String.self) ?? []
518
- let serviceUUIDs = services.map({(service) -> CBUUID in
519
- return CBUUID(string: service)
520
- })
521
- return serviceUUIDs
522
- }
523
-
524
- private func getDevice(_ call: CAPPluginCall, checkConnection: Bool = true) -> Device? {
525
- guard let deviceId = call.getString("deviceId") else {
526
- call.reject("deviceId required.")
527
- return nil
528
- }
529
- guard let device = self.deviceMap[deviceId] else {
530
- call.reject("Device not found. Call 'requestDevice', 'requestLEScan' or 'getDevices' first.")
531
- return nil
532
- }
533
- if checkConnection {
534
- guard device.isConnected() else {
535
- call.reject("Not connected to device.")
536
- return nil
537
- }
538
- }
539
- return device
540
- }
541
-
542
- private func getTimeout(_ call: CAPPluginCall, defaultTimeout: Double = DEFAULT_TIMEOUT) -> Double {
543
- guard let timeout = call.getDouble("timeout") else {
544
- return defaultTimeout
545
- }
546
- return timeout / 1000
547
- }
548
-
549
- private func getCharacteristic(_ call: CAPPluginCall) -> (CBUUID, CBUUID)? {
550
- guard let service = call.getString("service") else {
551
- call.reject("Service UUID required.")
552
- return nil
553
- }
554
- let serviceUUID = CBUUID(string: service)
555
-
556
- guard let characteristic = call.getString("characteristic") else {
557
- call.reject("Characteristic UUID required.")
558
- return nil
559
- }
560
- let characteristicUUID = CBUUID(string: characteristic)
561
- return (serviceUUID, characteristicUUID)
562
- }
563
-
564
- private func getDescriptor(_ call: CAPPluginCall) -> (CBUUID, CBUUID, CBUUID)? {
565
- guard let characteristic = getCharacteristic(call) else {
566
- return nil
567
- }
568
- guard let descriptor = call.getString("descriptor") else {
569
- call.reject("Descriptor UUID required.")
570
- return nil
571
- }
572
- let descriptorUUID = CBUUID(string: descriptor)
573
-
574
- return (characteristic.0, characteristic.1, descriptorUUID)
575
- }
576
-
577
- private func getBleDevice(_ device: Device) -> BleDevice {
578
- var bleDevice = [
579
- "deviceId": device.getId()
580
- ]
581
- if device.getName() != nil {
582
- bleDevice["name"] = device.getName()
583
- }
584
- return bleDevice
585
- }
586
-
587
- private func getScanResult(_ device: Device, _ advertisementData: [String: Any], _ rssi: NSNumber) -> [String: Any] {
588
- var data = [
589
- "device": self.getBleDevice(device),
590
- "rssi": rssi,
591
- "txPower": advertisementData[CBAdvertisementDataTxPowerLevelKey] ?? 127,
592
- "uuids": (advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] ?? []).map({(uuid) -> String in
593
- return cbuuidToString(uuid)
594
- })
595
- ]
596
-
597
- let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String
598
- if localName != nil {
599
- data["localName"] = localName
600
- }
601
-
602
- let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
603
- if manufacturerData != nil {
604
- data["manufacturerData"] = self.getManufacturerData(data: manufacturerData!)
605
- }
606
-
607
- let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]
608
- if serviceData != nil {
609
- data["serviceData"] = self.getServiceData(data: serviceData!)
610
- }
611
- return data
612
- }
613
-
614
- private func getManufacturerData(data: Data) -> [String: String] {
615
- var company = 0
616
- var rest = ""
617
- for (index, byte) in data.enumerated() {
618
- if index == 0 {
619
- company += Int(byte)
620
- } else if index == 1 {
621
- company += Int(byte) * 256
622
- } else {
623
- rest += String(format: "%02hhx ", byte)
624
- }
625
- }
626
- return [String(company): rest]
627
- }
628
-
629
- private func getServiceData(data: [CBUUID: Data]) -> [String: String] {
630
- var result: [String: String] = [:]
631
- for (key, value) in data {
632
- result[cbuuidToString(key)] = dataToString(value)
633
- }
634
- return result
635
- }
636
- }
1
+ // swiftlint:disable identifier_name
2
+ // swiftlint:disable type_body_length
3
+ import Foundation
4
+ import Capacitor
5
+ import CoreBluetooth
6
+
7
+ let CONNECTION_TIMEOUT: Double = 10
8
+ let DEFAULT_TIMEOUT: Double = 5
9
+
10
+ struct ManufacturerDataFilter {
11
+ let companyIdentifier: UInt16
12
+ let dataPrefix: Data?
13
+ let mask: Data?
14
+ }
15
+
16
+ @objc(BluetoothLe)
17
+ public class BluetoothLe: CAPPlugin {
18
+ typealias BleDevice = [String: Any]
19
+ typealias BleService = [String: Any]
20
+ typealias BleCharacteristic = [String: Any]
21
+ typealias BleDescriptor = [String: Any]
22
+ private var deviceManager: DeviceManager?
23
+ private var deviceMap = [String: Device]()
24
+ private var displayStrings = [String: String]()
25
+
26
+ override public func load() {
27
+ self.displayStrings = self.getDisplayStrings()
28
+ }
29
+
30
+ @objc func initialize(_ call: CAPPluginCall) {
31
+ self.deviceManager = DeviceManager(self.bridge?.viewController, self.displayStrings, {(success, message) in
32
+ if success {
33
+ call.resolve()
34
+ } else {
35
+ call.reject(message)
36
+ }
37
+ })
38
+ }
39
+
40
+ @objc func isEnabled(_ call: CAPPluginCall) {
41
+ guard let deviceManager = self.getDeviceManager(call) else { return }
42
+ let enabled: Bool = deviceManager.isEnabled()
43
+ call.resolve(["value": enabled])
44
+ }
45
+
46
+ @objc func requestEnable(_ call: CAPPluginCall) {
47
+ call.unavailable("requestEnable is not available on iOS.")
48
+ }
49
+
50
+ @objc func enable(_ call: CAPPluginCall) {
51
+ call.unavailable("enable is not available on iOS.")
52
+ }
53
+
54
+ @objc func disable(_ call: CAPPluginCall) {
55
+ call.unavailable("disable is not available on iOS.")
56
+ }
57
+
58
+ @objc func startEnabledNotifications(_ call: CAPPluginCall) {
59
+ guard let deviceManager = self.getDeviceManager(call) else { return }
60
+ deviceManager.registerStateReceiver({(enabled) in
61
+ self.notifyListeners("onEnabledChanged", data: ["value": enabled])
62
+ })
63
+ call.resolve()
64
+ }
65
+
66
+ @objc func stopEnabledNotifications(_ call: CAPPluginCall) {
67
+ guard let deviceManager = self.getDeviceManager(call) else { return }
68
+ deviceManager.unregisterStateReceiver()
69
+ call.resolve()
70
+ }
71
+
72
+ @objc func isLocationEnabled(_ call: CAPPluginCall) {
73
+ call.unavailable("isLocationEnabled is not available on iOS.")
74
+ }
75
+
76
+ @objc func openLocationSettings(_ call: CAPPluginCall) {
77
+ call.unavailable("openLocationSettings is not available on iOS.")
78
+ }
79
+
80
+ @objc func openBluetoothSettings(_ call: CAPPluginCall) {
81
+ call.unavailable("openBluetoothSettings is not available on iOS.")
82
+ }
83
+
84
+ @objc func openAppSettings(_ call: CAPPluginCall) {
85
+ guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
86
+ call.reject("Cannot open app settings.")
87
+ return
88
+ }
89
+
90
+ DispatchQueue.main.async {
91
+ if UIApplication.shared.canOpenURL(settingsUrl) {
92
+ UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
93
+ call.resolve([
94
+ "value": success
95
+ ])
96
+ })
97
+ } else {
98
+ call.reject("Cannot open app settings.")
99
+ }
100
+ }
101
+ }
102
+
103
+ @objc func setDisplayStrings(_ call: CAPPluginCall) {
104
+ for key in ["noDeviceFound", "availableDevices", "scanning", "cancel"] {
105
+ if call.getString(key) != nil {
106
+ self.displayStrings[key] = call.getString(key)
107
+ }
108
+ }
109
+ call.resolve()
110
+ }
111
+
112
+ @objc func requestDevice(_ call: CAPPluginCall) {
113
+ guard let deviceManager = self.getDeviceManager(call) else { return }
114
+ deviceManager.setDisplayStrings(self.displayStrings)
115
+
116
+ let serviceUUIDs = self.getServiceUUIDs(call)
117
+ let name = call.getString("name")
118
+ let namePrefix = call.getString("namePrefix")
119
+ let manufacturerDataFilters = self.getManufacturerDataFilters(call)
120
+
121
+ deviceManager.startScanning(
122
+ serviceUUIDs,
123
+ name,
124
+ namePrefix,
125
+ manufacturerDataFilters,
126
+ false,
127
+ true,
128
+ 30,
129
+ {(success, message) in
130
+ if success {
131
+ guard let device = deviceManager.getDevice(message) else {
132
+ call.reject("Device not found.")
133
+ return
134
+ }
135
+ self.deviceMap[device.getId()] = device
136
+ let bleDevice: BleDevice = self.getBleDevice(device)
137
+ call.resolve(bleDevice)
138
+ } else {
139
+ call.reject(message)
140
+ }
141
+ },
142
+ { (_, _, _) in }
143
+ )
144
+ }
145
+
146
+ @objc func requestLEScan(_ call: CAPPluginCall) {
147
+ guard let deviceManager = self.getDeviceManager(call) else { return }
148
+
149
+ let serviceUUIDs = self.getServiceUUIDs(call)
150
+ let name = call.getString("name")
151
+ let namePrefix = call.getString("namePrefix")
152
+ let allowDuplicates = call.getBool("allowDuplicates", false)
153
+ let manufacturerDataFilters = self.getManufacturerDataFilters(call)
154
+
155
+ deviceManager.startScanning(
156
+ serviceUUIDs,
157
+ name,
158
+ namePrefix,
159
+ manufacturerDataFilters,
160
+ allowDuplicates,
161
+ false,
162
+ nil,
163
+ { (success, message) in
164
+ if success {
165
+ call.resolve()
166
+ } else {
167
+ call.reject(message)
168
+ }
169
+ }, { (device, advertisementData, rssi) in
170
+ self.deviceMap[device.getId()] = device
171
+ let data = self.getScanResult(device, advertisementData, rssi)
172
+ self.notifyListeners("onScanResult", data: data)
173
+ }
174
+ )
175
+ }
176
+
177
+ @objc func stopLEScan(_ call: CAPPluginCall) {
178
+ guard let deviceManager = self.getDeviceManager(call) else { return }
179
+ deviceManager.stopScan()
180
+ call.resolve()
181
+ }
182
+
183
+ @objc func getDevices(_ call: CAPPluginCall) {
184
+ guard let deviceManager = self.getDeviceManager(call) else { return }
185
+ guard let deviceIds = call.getArray("deviceIds", String.self) else {
186
+ call.reject("deviceIds must be provided")
187
+ return
188
+ }
189
+ let deviceUUIDs: [UUID] = deviceIds.compactMap({ deviceId in
190
+ return UUID(uuidString: deviceId)
191
+ })
192
+ let peripherals = deviceManager.getDevices(deviceUUIDs)
193
+ let bleDevices: [BleDevice] = peripherals.map({peripheral in
194
+ let deviceId = peripheral.identifier.uuidString
195
+ guard let device = self.deviceMap[deviceId] else {
196
+ let newDevice = Device(peripheral)
197
+ self.deviceMap[newDevice.getId()] = newDevice
198
+ return self.getBleDevice(newDevice)
199
+ }
200
+ return self.getBleDevice(device)
201
+ })
202
+ call.resolve(["devices": bleDevices])
203
+ }
204
+
205
+ @objc func getConnectedDevices(_ call: CAPPluginCall) {
206
+ guard let deviceManager = self.getDeviceManager(call) else { return }
207
+ guard let services = call.getArray("services", String.self) else {
208
+ call.reject("services must be provided")
209
+ return
210
+ }
211
+ let serviceUUIDs: [CBUUID] = services.compactMap({ service in
212
+ return CBUUID(string: service)
213
+ })
214
+ let peripherals = deviceManager.getConnectedDevices(serviceUUIDs)
215
+ let bleDevices: [BleDevice] = peripherals.map({peripheral in
216
+ let deviceId = peripheral.identifier.uuidString
217
+ guard let device = self.deviceMap[deviceId] else {
218
+ let newDevice = Device(peripheral)
219
+ self.deviceMap[newDevice.getId()] = newDevice
220
+ return self.getBleDevice(newDevice)
221
+ }
222
+ return self.getBleDevice(device)
223
+ })
224
+ call.resolve(["devices": bleDevices])
225
+ }
226
+
227
+ @objc func connect(_ call: CAPPluginCall) {
228
+ guard self.getDeviceManager(call) != nil else { return }
229
+ guard let device = self.getDevice(call, checkConnection: false) else { return }
230
+ let timeout = self.getTimeout(call, defaultTimeout: CONNECTION_TIMEOUT)
231
+ device.setOnConnected(timeout, {(success, message) in
232
+ if success {
233
+ // only resolve after service discovery
234
+ call.resolve()
235
+ } else {
236
+ call.reject(message)
237
+ }
238
+ })
239
+ self.deviceManager?.setOnDisconnected(device, {(_, _) in
240
+ let key = "disconnected|\(device.getId())"
241
+ self.notifyListeners(key, data: nil)
242
+ })
243
+ self.deviceManager?.connect(device, timeout, {(success, message) in
244
+ if success {
245
+ log("Connected to peripheral. Waiting for service discovery.")
246
+ } else {
247
+ call.reject(message)
248
+ }
249
+ })
250
+
251
+ }
252
+
253
+ @objc func createBond(_ call: CAPPluginCall) {
254
+ call.unavailable("createBond is not available on iOS.")
255
+ }
256
+
257
+ @objc func isBonded(_ call: CAPPluginCall) {
258
+ call.unavailable("isBonded is not available on iOS.")
259
+ }
260
+
261
+ @objc func getBondedDevices(_ call: CAPPluginCall) {
262
+ call.unavailable("getBondedDevices is not available on iOS.")
263
+ }
264
+
265
+ @objc func disconnect(_ call: CAPPluginCall) {
266
+ guard self.getDeviceManager(call) != nil else { return }
267
+ guard let device = self.getDevice(call, checkConnection: false) else { return }
268
+ let timeout = self.getTimeout(call)
269
+ self.deviceManager?.disconnect(device, timeout, {(success, message) in
270
+ if success {
271
+ call.resolve()
272
+ } else {
273
+ call.reject(message)
274
+ }
275
+ })
276
+ }
277
+
278
+ @objc func getServices(_ call: CAPPluginCall) {
279
+ guard self.getDeviceManager(call) != nil else { return }
280
+ guard let device = self.getDevice(call) else { return }
281
+ let services = device.getServices()
282
+ var bleServices = [BleService]()
283
+ for service in services {
284
+ var bleCharacteristics = [BleCharacteristic]()
285
+ for characteristic in service.characteristics ?? [] {
286
+ var bleDescriptors = [BleDescriptor]()
287
+ for descriptor in characteristic.descriptors ?? [] {
288
+ bleDescriptors.append([
289
+ "uuid": cbuuidToString(descriptor.uuid)
290
+ ])
291
+ }
292
+ bleCharacteristics.append([
293
+ "uuid": cbuuidToString(characteristic.uuid),
294
+ "properties": getProperties(characteristic),
295
+ "descriptors": bleDescriptors
296
+ ])
297
+ }
298
+ bleServices.append([
299
+ "uuid": cbuuidToString(service.uuid),
300
+ "characteristics": bleCharacteristics
301
+ ])
302
+ }
303
+ call.resolve(["services": bleServices])
304
+ }
305
+
306
+ private func getProperties(_ characteristic: CBCharacteristic) -> [String: Bool] {
307
+ return [
308
+ "broadcast": characteristic.properties.contains(CBCharacteristicProperties.broadcast),
309
+ "read": characteristic.properties.contains(CBCharacteristicProperties.read),
310
+ "writeWithoutResponse": characteristic.properties.contains(CBCharacteristicProperties.writeWithoutResponse),
311
+ "write": characteristic.properties.contains(CBCharacteristicProperties.write),
312
+ "notify": characteristic.properties.contains(CBCharacteristicProperties.notify),
313
+ "indicate": characteristic.properties.contains(CBCharacteristicProperties.indicate),
314
+ "authenticatedSignedWrites": characteristic.properties.contains(CBCharacteristicProperties.authenticatedSignedWrites),
315
+ "extendedProperties": characteristic.properties.contains(CBCharacteristicProperties.extendedProperties),
316
+ "notifyEncryptionRequired": characteristic.properties.contains(CBCharacteristicProperties.notifyEncryptionRequired),
317
+ "indicateEncryptionRequired": characteristic.properties.contains(CBCharacteristicProperties.indicateEncryptionRequired)
318
+ ]
319
+ }
320
+
321
+ @objc func discoverServices(_ call: CAPPluginCall) {
322
+ guard self.getDeviceManager(call) != nil else { return }
323
+ guard let device = self.getDevice(call) else { return }
324
+ let timeout = self.getTimeout(call)
325
+ device.discoverServices(timeout, {(success, value) in
326
+ if success {
327
+ call.resolve()
328
+ } else {
329
+ call.reject(value)
330
+ }
331
+ })
332
+ }
333
+
334
+ @objc func getMtu(_ call: CAPPluginCall) {
335
+ guard self.getDeviceManager(call) != nil else { return }
336
+ guard let device = self.getDevice(call) else { return }
337
+ call.resolve([
338
+ "value": device.getMtu()
339
+ ])
340
+ }
341
+
342
+ @objc func requestConnectionPriority(_ call: CAPPluginCall) {
343
+ call.unavailable("requestConnectionPriority is not available on iOS.")
344
+ }
345
+
346
+ @objc func readRssi(_ call: CAPPluginCall) {
347
+ guard self.getDeviceManager(call) != nil else { return }
348
+ guard let device = self.getDevice(call) else { return }
349
+ let timeout = self.getTimeout(call)
350
+ device.readRssi(timeout, {(success, value) in
351
+ if success {
352
+ call.resolve([
353
+ "value": value
354
+ ])
355
+ } else {
356
+ call.reject(value)
357
+ }
358
+ })
359
+ }
360
+
361
+ @objc func read(_ call: CAPPluginCall) {
362
+ guard self.getDeviceManager(call) != nil else { return }
363
+ guard let device = self.getDevice(call) else { return }
364
+ guard let characteristic = self.getCharacteristic(call) else { return }
365
+ let timeout = self.getTimeout(call)
366
+ device.read(characteristic.0, characteristic.1, timeout, {(success, value) in
367
+ if success {
368
+ call.resolve([
369
+ "value": value
370
+ ])
371
+ } else {
372
+ call.reject(value)
373
+ }
374
+ })
375
+ }
376
+
377
+ @objc func write(_ call: CAPPluginCall) {
378
+ guard self.getDeviceManager(call) != nil else { return }
379
+ guard let device = self.getDevice(call) else { return }
380
+ guard let characteristic = self.getCharacteristic(call) else { return }
381
+ guard let value = call.getString("value") else {
382
+ call.reject("value must be provided")
383
+ return
384
+ }
385
+ let writeType = CBCharacteristicWriteType.withResponse
386
+ let timeout = self.getTimeout(call)
387
+ device.write(
388
+ characteristic.0,
389
+ characteristic.1,
390
+ value,
391
+ writeType,
392
+ timeout, {(success, value) in
393
+ if success {
394
+ call.resolve()
395
+ } else {
396
+ call.reject(value)
397
+ }
398
+ })
399
+ }
400
+
401
+ @objc func writeWithoutResponse(_ call: CAPPluginCall) {
402
+ guard self.getDeviceManager(call) != nil else { return }
403
+ guard let device = self.getDevice(call) else { return }
404
+ guard let characteristic = self.getCharacteristic(call) else { return }
405
+ guard let value = call.getString("value") else {
406
+ call.reject("value must be provided")
407
+ return
408
+ }
409
+ let writeType = CBCharacteristicWriteType.withoutResponse
410
+ let timeout = self.getTimeout(call)
411
+ device.write(
412
+ characteristic.0,
413
+ characteristic.1,
414
+ value,
415
+ writeType,
416
+ timeout, {(success, value) in
417
+ if success {
418
+ call.resolve()
419
+ } else {
420
+ call.reject(value)
421
+ }
422
+ })
423
+ }
424
+
425
+ @objc func readDescriptor(_ call: CAPPluginCall) {
426
+ guard self.getDeviceManager(call) != nil else { return }
427
+ guard let device = self.getDevice(call) else { return }
428
+ guard let descriptor = self.getDescriptor(call) else { return }
429
+ let timeout = self.getTimeout(call)
430
+ device.readDescriptor(
431
+ descriptor.0,
432
+ descriptor.1,
433
+ descriptor.2,
434
+ timeout, {(success, value) in
435
+ if success {
436
+ call.resolve([
437
+ "value": value
438
+ ])
439
+ } else {
440
+ call.reject(value)
441
+ }
442
+ })
443
+ }
444
+
445
+ @objc func writeDescriptor(_ call: CAPPluginCall) {
446
+ guard self.getDeviceManager(call) != nil else { return }
447
+ guard let device = self.getDevice(call) else { return }
448
+ guard let descriptor = self.getDescriptor(call) else { return }
449
+ guard let value = call.getString("value") else {
450
+ call.reject("value must be provided")
451
+ return
452
+ }
453
+ let timeout = self.getTimeout(call)
454
+ device.writeDescriptor(
455
+ descriptor.0,
456
+ descriptor.1,
457
+ descriptor.2,
458
+ value,
459
+ timeout, {(success, value) in
460
+ if success {
461
+ call.resolve()
462
+ } else {
463
+ call.reject(value)
464
+ }
465
+ })
466
+ }
467
+
468
+ @objc func startNotifications(_ call: CAPPluginCall) {
469
+ guard self.getDeviceManager(call) != nil else { return }
470
+ guard let device = self.getDevice(call) else { return }
471
+ guard let characteristic = self.getCharacteristic(call) else { return }
472
+ let timeout = self.getTimeout(call)
473
+ device.setNotifications(
474
+ characteristic.0,
475
+ characteristic.1,
476
+ true, {(_, value) in
477
+ let key = "notification|\(device.getId())|\(characteristic.0.uuidString.lowercased())|\(characteristic.1.uuidString.lowercased())"
478
+ self.notifyListeners(key, data: ["value": value])
479
+ },
480
+ timeout, {(success, value) in
481
+ if success {
482
+ call.resolve()
483
+ } else {
484
+ call.reject(value)
485
+ }
486
+ })
487
+ }
488
+
489
+ @objc func stopNotifications(_ call: CAPPluginCall) {
490
+ guard self.getDeviceManager(call) != nil else { return }
491
+ guard let device = self.getDevice(call) else { return }
492
+ guard let characteristic = self.getCharacteristic(call) else { return }
493
+ let timeout = self.getTimeout(call)
494
+ device.setNotifications(
495
+ characteristic.0,
496
+ characteristic.1,
497
+ false,
498
+ nil,
499
+ timeout, {(success, value) in
500
+ if success {
501
+ call.resolve()
502
+ } else {
503
+ call.reject(value)
504
+ }
505
+ })
506
+ }
507
+
508
+ private func getDisplayStrings() -> [String: String] {
509
+ let configDisplayStrings = getConfig().getObject("displayStrings") as? [String: String] ?? [String: String]()
510
+ var displayStrings = [String: String]()
511
+ displayStrings["noDeviceFound"] = configDisplayStrings["noDeviceFound"] ?? "No device found"
512
+ displayStrings["availableDevices"] = configDisplayStrings["availableDevices"] ?? "Available devices"
513
+ displayStrings["scanning"] = configDisplayStrings["scanning"] ?? "Scanning..."
514
+ displayStrings["cancel"] = configDisplayStrings["cancel"] ?? "Cancel"
515
+ return displayStrings
516
+ }
517
+
518
+ private func getDeviceManager(_ call: CAPPluginCall) -> DeviceManager? {
519
+ guard let deviceManager = self.deviceManager else {
520
+ call.reject("Bluetooth LE not initialized.")
521
+ return nil
522
+ }
523
+ return deviceManager
524
+ }
525
+
526
+ private func getServiceUUIDs(_ call: CAPPluginCall) -> [CBUUID] {
527
+ let services = call.getArray("services", String.self) ?? []
528
+ let serviceUUIDs = services.map({(service) -> CBUUID in
529
+ return CBUUID(string: service)
530
+ })
531
+ return serviceUUIDs
532
+ }
533
+
534
+ private func getManufacturerDataFilters(_ call: CAPPluginCall) -> [ManufacturerDataFilter]? {
535
+ guard let manufacturerDataArray = call.getArray("manufacturerData") else {
536
+ return nil
537
+ }
538
+
539
+ var manufacturerDataFilters: [ManufacturerDataFilter] = []
540
+
541
+ for index in 0..<manufacturerDataArray.count {
542
+ guard let dataObject = manufacturerDataArray[index] as? JSObject,
543
+ let companyIdentifier = dataObject["companyIdentifier"] as? UInt16 else {
544
+ // Invalid or missing company identifier
545
+ return nil
546
+ }
547
+
548
+ let dataPrefix: Data? = {
549
+ guard let prefixArray = dataObject["dataPrefix"] as? [Int] else { return nil }
550
+ return Data(prefixArray.map { UInt8($0 & 0xFF) })
551
+ }()
552
+
553
+ let mask: Data? = {
554
+ guard let maskArray = dataObject["mask"] as? [Int] else { return nil }
555
+ return Data(maskArray.map { UInt8($0 & 0xFF) })
556
+ }()
557
+
558
+ let manufacturerFilter = ManufacturerDataFilter(
559
+ companyIdentifier: companyIdentifier,
560
+ dataPrefix: dataPrefix,
561
+ mask: mask
562
+ )
563
+
564
+ manufacturerDataFilters.append(manufacturerFilter)
565
+ }
566
+
567
+ return manufacturerDataFilters
568
+ }
569
+
570
+ private func getDevice(_ call: CAPPluginCall, checkConnection: Bool = true) -> Device? {
571
+ guard let deviceId = call.getString("deviceId") else {
572
+ call.reject("deviceId required.")
573
+ return nil
574
+ }
575
+ guard let device = self.deviceMap[deviceId] else {
576
+ call.reject("Device not found. Call 'requestDevice', 'requestLEScan' or 'getDevices' first.")
577
+ return nil
578
+ }
579
+ if checkConnection {
580
+ guard device.isConnected() else {
581
+ call.reject("Not connected to device.")
582
+ return nil
583
+ }
584
+ }
585
+ return device
586
+ }
587
+
588
+ private func getTimeout(_ call: CAPPluginCall, defaultTimeout: Double = DEFAULT_TIMEOUT) -> Double {
589
+ guard let timeout = call.getDouble("timeout") else {
590
+ return defaultTimeout
591
+ }
592
+ return timeout / 1000
593
+ }
594
+
595
+ private func getCharacteristic(_ call: CAPPluginCall) -> (CBUUID, CBUUID)? {
596
+ guard let service = call.getString("service") else {
597
+ call.reject("Service UUID required.")
598
+ return nil
599
+ }
600
+ let serviceUUID = CBUUID(string: service)
601
+
602
+ guard let characteristic = call.getString("characteristic") else {
603
+ call.reject("Characteristic UUID required.")
604
+ return nil
605
+ }
606
+ let characteristicUUID = CBUUID(string: characteristic)
607
+ return (serviceUUID, characteristicUUID)
608
+ }
609
+
610
+ private func getDescriptor(_ call: CAPPluginCall) -> (CBUUID, CBUUID, CBUUID)? {
611
+ guard let characteristic = getCharacteristic(call) else {
612
+ return nil
613
+ }
614
+ guard let descriptor = call.getString("descriptor") else {
615
+ call.reject("Descriptor UUID required.")
616
+ return nil
617
+ }
618
+ let descriptorUUID = CBUUID(string: descriptor)
619
+
620
+ return (characteristic.0, characteristic.1, descriptorUUID)
621
+ }
622
+
623
+ private func getBleDevice(_ device: Device) -> BleDevice {
624
+ var bleDevice = [
625
+ "deviceId": device.getId()
626
+ ]
627
+ if device.getName() != nil {
628
+ bleDevice["name"] = device.getName()
629
+ }
630
+ return bleDevice
631
+ }
632
+
633
+ private func getScanResult(_ device: Device, _ advertisementData: [String: Any], _ rssi: NSNumber) -> [String: Any] {
634
+ var data = [
635
+ "device": self.getBleDevice(device),
636
+ "rssi": rssi,
637
+ "txPower": advertisementData[CBAdvertisementDataTxPowerLevelKey] ?? 127,
638
+ "uuids": (advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] ?? []).map({(uuid) -> String in
639
+ return cbuuidToString(uuid)
640
+ })
641
+ ]
642
+
643
+ let localName = advertisementData[CBAdvertisementDataLocalNameKey] as? String
644
+ if localName != nil {
645
+ data["localName"] = localName
646
+ }
647
+
648
+ let manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
649
+ if manufacturerData != nil {
650
+ data["manufacturerData"] = self.getManufacturerData(data: manufacturerData!)
651
+ }
652
+
653
+ let serviceData = advertisementData[CBAdvertisementDataServiceDataKey] as? [CBUUID: Data]
654
+ if serviceData != nil {
655
+ data["serviceData"] = self.getServiceData(data: serviceData!)
656
+ }
657
+ return data
658
+ }
659
+
660
+ private func getManufacturerData(data: Data) -> [String: String] {
661
+ var company = 0
662
+ var rest = ""
663
+ for (index, byte) in data.enumerated() {
664
+ if index == 0 {
665
+ company += Int(byte)
666
+ } else if index == 1 {
667
+ company += Int(byte) * 256
668
+ } else {
669
+ rest += String(format: "%02hhx ", byte)
670
+ }
671
+ }
672
+ return [String(company): rest]
673
+ }
674
+
675
+ private func getServiceData(data: [CBUUID: Data]) -> [String: String] {
676
+ var result: [String: String] = [:]
677
+ for (key, value) in data {
678
+ result[cbuuidToString(key)] = dataToString(value)
679
+ }
680
+ return result
681
+ }
682
+ }