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