@capacitor-community/bluetooth-le 7.2.0 → 8.0.0-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.
- package/CapacitorCommunityBluetoothLe.podspec +17 -17
- package/LICENSE +21 -21
- package/Package.swift +28 -0
- package/README.md +68 -161
- package/android/build.gradle +71 -68
- package/android/src/main/AndroidManifest.xml +22 -22
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BluetoothLe.kt +1094 -1070
- 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 -767
- 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 +906 -849
- package/dist/esm/bleClient.d.ts +278 -278
- package/dist/esm/bleClient.js +361 -347
- 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 -34
- package/dist/esm/conversion.js +134 -84
- package/dist/esm/conversion.js.map +1 -1
- package/dist/esm/definitions.d.ts +352 -313
- 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/queue.js.map +1 -1
- 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/validators.js.map +1 -1
- package/dist/esm/web.d.ts +57 -56
- package/dist/esm/web.js +403 -340
- package/dist/esm/web.js.map +1 -1
- package/dist/plugin.cjs.js +967 -837
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +967 -837
- package/dist/plugin.js.map +1 -1
- package/ios/{Plugin → Sources/BluetoothLe}/Conversion.swift +83 -83
- package/ios/{Plugin → Sources/BluetoothLe}/Device.swift +423 -423
- package/ios/Sources/BluetoothLe/DeviceListView.swift +121 -0
- package/ios/{Plugin → Sources/BluetoothLe}/DeviceManager.swift +503 -401
- package/ios/{Plugin → Sources/BluetoothLe}/Logging.swift +8 -8
- package/ios/{Plugin → Sources/BluetoothLe}/Plugin.swift +775 -682
- package/ios/{Plugin → Sources/BluetoothLe}/ThreadSafeDictionary.swift +15 -13
- package/ios/Tests/BluetoothLeTests/ConversionTests.swift +55 -0
- package/ios/Tests/BluetoothLeTests/PluginTests.swift +27 -0
- package/package.json +115 -101
- package/ios/Plugin/Info.plist +0 -24
- package/ios/Plugin/Plugin.h +0 -10
- package/ios/Plugin/Plugin.m +0 -41
|
@@ -1,423 +1,423 @@
|
|
|
1
|
-
// swiftlint:disable type_body_length
|
|
2
|
-
import Foundation
|
|
3
|
-
import CoreBluetooth
|
|
4
|
-
|
|
5
|
-
class Device: NSObject, CBPeripheralDelegate {
|
|
6
|
-
typealias Callback = (_ success: Bool, _ value: String) -> Void
|
|
7
|
-
|
|
8
|
-
private var peripheral: CBPeripheral!
|
|
9
|
-
private var callbackMap = ThreadSafeDictionary<String, Callback>()
|
|
10
|
-
private var timeoutMap = [String: DispatchWorkItem]()
|
|
11
|
-
private var servicesCount = 0
|
|
12
|
-
private var servicesDiscovered = 0
|
|
13
|
-
private var characteristicsCount = 0
|
|
14
|
-
private var characteristicsDiscovered = 0
|
|
15
|
-
|
|
16
|
-
init(
|
|
17
|
-
_ peripheral: CBPeripheral
|
|
18
|
-
) {
|
|
19
|
-
super.init()
|
|
20
|
-
self.peripheral = peripheral
|
|
21
|
-
self.peripheral.delegate = self
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
func getName() -> String? {
|
|
25
|
-
return self.peripheral.name
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
func getId() -> String {
|
|
29
|
-
return self.peripheral.identifier.uuidString
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
func isConnected() -> Bool {
|
|
33
|
-
return self.peripheral.state == CBPeripheralState.connected
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
func getPeripheral() -> CBPeripheral {
|
|
37
|
-
return self.peripheral
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
func setOnConnected(
|
|
41
|
-
_ connectionTimeout: Double,
|
|
42
|
-
_ callback: @escaping Callback
|
|
43
|
-
) {
|
|
44
|
-
let key = "connect"
|
|
45
|
-
self.callbackMap[key] = callback
|
|
46
|
-
self.setTimeout(key, "Connection timeout", connectionTimeout)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
func peripheral(
|
|
50
|
-
_ peripheral: CBPeripheral,
|
|
51
|
-
didDiscoverServices error: Error?
|
|
52
|
-
) {
|
|
53
|
-
log("didDiscoverServices")
|
|
54
|
-
if error != nil {
|
|
55
|
-
log("Error", error!.localizedDescription)
|
|
56
|
-
return
|
|
57
|
-
}
|
|
58
|
-
self.servicesCount = peripheral.services?.count ?? 0
|
|
59
|
-
self.servicesDiscovered = 0
|
|
60
|
-
self.characteristicsCount = 0
|
|
61
|
-
self.characteristicsDiscovered = 0
|
|
62
|
-
for service in peripheral.services ?? [] {
|
|
63
|
-
peripheral.discoverCharacteristics(nil, for: service)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
func peripheral(
|
|
68
|
-
_ peripheral: CBPeripheral,
|
|
69
|
-
didDiscoverCharacteristicsFor service: CBService,
|
|
70
|
-
error: Error?
|
|
71
|
-
) {
|
|
72
|
-
self.servicesDiscovered += 1
|
|
73
|
-
log("didDiscoverCharacteristicsFor", self.servicesDiscovered, self.servicesCount)
|
|
74
|
-
self.characteristicsCount += service.characteristics?.count ?? 0
|
|
75
|
-
for characteristic in service.characteristics ?? [] {
|
|
76
|
-
peripheral.discoverDescriptors(for: characteristic)
|
|
77
|
-
}
|
|
78
|
-
// if the last service does not have characteristics, resolve the connect call now
|
|
79
|
-
if self.servicesDiscovered >= self.servicesCount && self.characteristicsDiscovered >= self.characteristicsCount {
|
|
80
|
-
self.resolve("connect", "Connection successful.")
|
|
81
|
-
self.resolve("discoverServices", "Services discovered.")
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
func peripheral(
|
|
86
|
-
_ peripheral: CBPeripheral,
|
|
87
|
-
didDiscoverDescriptorsFor characteristic: CBCharacteristic,
|
|
88
|
-
error: Error?
|
|
89
|
-
) {
|
|
90
|
-
self.characteristicsDiscovered += 1
|
|
91
|
-
if self.servicesDiscovered >= self.servicesCount && self.characteristicsDiscovered >= self.characteristicsCount {
|
|
92
|
-
self.resolve("connect", "Connection successful.")
|
|
93
|
-
self.resolve("discoverServices", "Services discovered.")
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
func getServices() -> [CBService] {
|
|
98
|
-
return self.peripheral.services ?? []
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
func discoverServices(
|
|
102
|
-
_ timeout: Double,
|
|
103
|
-
_ callback: @escaping Callback
|
|
104
|
-
) {
|
|
105
|
-
let key = "discoverServices"
|
|
106
|
-
self.callbackMap[key] = callback
|
|
107
|
-
self.peripheral.discoverServices(nil)
|
|
108
|
-
self.setTimeout(key, "Service discovery timeout.", timeout)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
func getMtu() -> Int {
|
|
112
|
-
// maximumWriteValueLength is 3 bytes less than ATT MTU
|
|
113
|
-
return self.peripheral.maximumWriteValueLength(for: .withoutResponse) + 3
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
func readRssi(
|
|
117
|
-
_ timeout: Double,
|
|
118
|
-
_ callback: @escaping Callback
|
|
119
|
-
) {
|
|
120
|
-
let key = "readRssi"
|
|
121
|
-
self.callbackMap[key] = callback
|
|
122
|
-
log("Reading RSSI value")
|
|
123
|
-
self.peripheral.readRSSI()
|
|
124
|
-
self.setTimeout(key, "Reading RSSI timeout.", timeout)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
func peripheral(
|
|
128
|
-
_ peripheral: CBPeripheral,
|
|
129
|
-
didReadRSSI RSSI: NSNumber,
|
|
130
|
-
error: Error?
|
|
131
|
-
) {
|
|
132
|
-
let key = "readRssi"
|
|
133
|
-
if error != nil {
|
|
134
|
-
self.reject(key, error!.localizedDescription)
|
|
135
|
-
return
|
|
136
|
-
}
|
|
137
|
-
self.resolve(key, RSSI.stringValue)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
private func getCharacteristic(
|
|
141
|
-
_ serviceUUID: CBUUID,
|
|
142
|
-
_ characteristicUUID: CBUUID
|
|
143
|
-
) -> CBCharacteristic? {
|
|
144
|
-
for service in peripheral.services ?? [] {
|
|
145
|
-
if service.uuid == serviceUUID {
|
|
146
|
-
for characteristic in service.characteristics ?? [] {
|
|
147
|
-
if characteristic.uuid == characteristicUUID {
|
|
148
|
-
return characteristic
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return nil
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
private func getDescriptor(
|
|
157
|
-
_ serviceUUID: CBUUID,
|
|
158
|
-
_ characteristicUUID: CBUUID,
|
|
159
|
-
_ descriptorUUID: CBUUID
|
|
160
|
-
) -> CBDescriptor? {
|
|
161
|
-
guard let characteristic = self.getCharacteristic(serviceUUID, characteristicUUID) else {
|
|
162
|
-
return nil
|
|
163
|
-
}
|
|
164
|
-
for descriptor in characteristic.descriptors ?? [] {
|
|
165
|
-
if descriptor.uuid == descriptorUUID {
|
|
166
|
-
return descriptor
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return nil
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
func read(
|
|
173
|
-
_ serviceUUID: CBUUID,
|
|
174
|
-
_ characteristicUUID: CBUUID,
|
|
175
|
-
_ timeout: Double,
|
|
176
|
-
_ callback: @escaping Callback
|
|
177
|
-
) {
|
|
178
|
-
let key = "read|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)"
|
|
179
|
-
self.callbackMap[key] = callback
|
|
180
|
-
guard let characteristic = self.getCharacteristic(serviceUUID, characteristicUUID) else {
|
|
181
|
-
self.reject(key, "Characteristic not found.")
|
|
182
|
-
return
|
|
183
|
-
}
|
|
184
|
-
log("Reading value")
|
|
185
|
-
self.peripheral.readValue(for: characteristic)
|
|
186
|
-
self.setTimeout(key, "Read timeout.", timeout)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
func peripheral(
|
|
190
|
-
_ peripheral: CBPeripheral,
|
|
191
|
-
didUpdateValueFor characteristic: CBCharacteristic,
|
|
192
|
-
error: Error?
|
|
193
|
-
) {
|
|
194
|
-
let key = self.getKey("read", characteristic)
|
|
195
|
-
let notifyKey = self.getKey("notification", characteristic)
|
|
196
|
-
if error != nil {
|
|
197
|
-
self.reject(key, error!.localizedDescription)
|
|
198
|
-
return
|
|
199
|
-
}
|
|
200
|
-
if characteristic.value == nil {
|
|
201
|
-
self.reject(key, "Characteristic contains no value.")
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
// reading
|
|
205
|
-
let valueString = dataToString(characteristic.value!)
|
|
206
|
-
self.resolve(key, valueString)
|
|
207
|
-
|
|
208
|
-
// notifications
|
|
209
|
-
let callback = self.callbackMap[notifyKey]
|
|
210
|
-
if callback != nil {
|
|
211
|
-
callback!(true, valueString)
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
func readDescriptor(
|
|
216
|
-
_ serviceUUID: CBUUID,
|
|
217
|
-
_ characteristicUUID: CBUUID,
|
|
218
|
-
_ descriptorUUID: CBUUID,
|
|
219
|
-
_ timeout: Double,
|
|
220
|
-
_ callback: @escaping Callback
|
|
221
|
-
) {
|
|
222
|
-
let key = "readDescriptor|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)|\(descriptorUUID.uuidString)"
|
|
223
|
-
self.callbackMap[key] = callback
|
|
224
|
-
guard let descriptor = self.getDescriptor(serviceUUID, characteristicUUID, descriptorUUID) else {
|
|
225
|
-
self.reject(key, "Descriptor not found.")
|
|
226
|
-
return
|
|
227
|
-
}
|
|
228
|
-
log("Reading descriptor value")
|
|
229
|
-
self.peripheral.readValue(for: descriptor)
|
|
230
|
-
self.setTimeout(key, "Read descriptor timeout.", timeout)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
func peripheral(
|
|
234
|
-
_ peripheral: CBPeripheral,
|
|
235
|
-
didUpdateValueFor descriptor: CBDescriptor,
|
|
236
|
-
error: Error?
|
|
237
|
-
) {
|
|
238
|
-
let key = self.getKey("readDescriptor", descriptor)
|
|
239
|
-
if error != nil {
|
|
240
|
-
self.reject(key, error!.localizedDescription)
|
|
241
|
-
return
|
|
242
|
-
}
|
|
243
|
-
if descriptor.value == nil {
|
|
244
|
-
self.reject(key, "Descriptor contains no value.")
|
|
245
|
-
return
|
|
246
|
-
}
|
|
247
|
-
let valueString = descriptorValueToString(descriptor.value!)
|
|
248
|
-
self.resolve(key, valueString)
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
func write(
|
|
252
|
-
_ serviceUUID: CBUUID,
|
|
253
|
-
_ characteristicUUID: CBUUID,
|
|
254
|
-
_ value: String,
|
|
255
|
-
_ writeType: CBCharacteristicWriteType,
|
|
256
|
-
_ timeout: Double,
|
|
257
|
-
_ callback: @escaping Callback
|
|
258
|
-
) {
|
|
259
|
-
let key = "write|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)"
|
|
260
|
-
self.callbackMap[key] = callback
|
|
261
|
-
guard let characteristic = self.getCharacteristic(serviceUUID, characteristicUUID) else {
|
|
262
|
-
self.reject(key, "Characteristic not found.")
|
|
263
|
-
return
|
|
264
|
-
}
|
|
265
|
-
let data: Data = stringToData(value)
|
|
266
|
-
self.peripheral.writeValue(data, for: characteristic, type: writeType)
|
|
267
|
-
if writeType == CBCharacteristicWriteType.withResponse {
|
|
268
|
-
self.setTimeout(key, "Write timeout.", timeout)
|
|
269
|
-
} else {
|
|
270
|
-
self.resolve(key, "Successfully written value.")
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
func peripheral(
|
|
275
|
-
_ peripheral: CBPeripheral,
|
|
276
|
-
didWriteValueFor characteristic: CBCharacteristic,
|
|
277
|
-
error: Error?
|
|
278
|
-
) {
|
|
279
|
-
let key = self.getKey("write", characteristic)
|
|
280
|
-
if error != nil {
|
|
281
|
-
self.reject(key, error!.localizedDescription)
|
|
282
|
-
return
|
|
283
|
-
}
|
|
284
|
-
self.resolve(key, "Successfully written value.")
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
func writeDescriptor(
|
|
288
|
-
_ serviceUUID: CBUUID,
|
|
289
|
-
_ characteristicUUID: CBUUID,
|
|
290
|
-
_ descriptorUUID: CBUUID,
|
|
291
|
-
_ value: String,
|
|
292
|
-
_ timeout: Double,
|
|
293
|
-
_ callback: @escaping Callback
|
|
294
|
-
) {
|
|
295
|
-
let key = "writeDescriptor|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)|\(descriptorUUID.uuidString)"
|
|
296
|
-
self.callbackMap[key] = callback
|
|
297
|
-
guard let descriptor = self.getDescriptor(serviceUUID, characteristicUUID, descriptorUUID) else {
|
|
298
|
-
self.reject(key, "Descriptor not found.")
|
|
299
|
-
return
|
|
300
|
-
}
|
|
301
|
-
let data: Data = stringToData(value)
|
|
302
|
-
self.peripheral.writeValue(data, for: descriptor)
|
|
303
|
-
self.setTimeout(key, "Write descriptor timeout.", timeout)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
func peripheral(
|
|
307
|
-
_ peripheral: CBPeripheral,
|
|
308
|
-
didWriteValueFor descriptor: CBDescriptor,
|
|
309
|
-
error: Error?
|
|
310
|
-
) {
|
|
311
|
-
let key = self.getKey("writeDescriptor", descriptor)
|
|
312
|
-
if error != nil {
|
|
313
|
-
self.reject(key, error!.localizedDescription)
|
|
314
|
-
return
|
|
315
|
-
}
|
|
316
|
-
self.resolve(key, "Successfully written descriptor value.")
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
func setNotifications(
|
|
320
|
-
_ serviceUUID: CBUUID,
|
|
321
|
-
_ characteristicUUID: CBUUID,
|
|
322
|
-
_ enable: Bool,
|
|
323
|
-
_ notifyCallback: Callback?,
|
|
324
|
-
_ timeout: Double,
|
|
325
|
-
_ callback: @escaping Callback
|
|
326
|
-
) {
|
|
327
|
-
let key = "setNotifications|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)"
|
|
328
|
-
let notifyKey = "notification|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)"
|
|
329
|
-
self.callbackMap[key] = callback
|
|
330
|
-
if notifyCallback != nil {
|
|
331
|
-
self.callbackMap[notifyKey] = notifyCallback
|
|
332
|
-
}
|
|
333
|
-
guard let characteristic = self.getCharacteristic(serviceUUID, characteristicUUID) else {
|
|
334
|
-
self.reject(key, "Characteristic not found.")
|
|
335
|
-
return
|
|
336
|
-
}
|
|
337
|
-
log("Set notifications", enable)
|
|
338
|
-
self.peripheral.setNotifyValue(enable, for: characteristic)
|
|
339
|
-
self.setTimeout(key, "Set notifications timeout.", timeout)
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
func peripheral(
|
|
343
|
-
_ peripheral: CBPeripheral,
|
|
344
|
-
didUpdateNotificationStateFor characteristic: CBCharacteristic,
|
|
345
|
-
error: Error?
|
|
346
|
-
) {
|
|
347
|
-
let key = self.getKey("setNotifications", characteristic)
|
|
348
|
-
if error != nil {
|
|
349
|
-
self.reject(key, error!.localizedDescription)
|
|
350
|
-
return
|
|
351
|
-
}
|
|
352
|
-
self.resolve(key, "Successfully set notifications.")
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
private func getKey(
|
|
356
|
-
_ prefix: String,
|
|
357
|
-
_ characteristic: CBCharacteristic?
|
|
358
|
-
) -> String {
|
|
359
|
-
let serviceUUIDString: String
|
|
360
|
-
let service: CBService? = characteristic?.service
|
|
361
|
-
if service != nil {
|
|
362
|
-
serviceUUIDString = cbuuidToStringUppercase(service!.uuid)
|
|
363
|
-
} else {
|
|
364
|
-
serviceUUIDString = "UNKNOWN-SERVICE"
|
|
365
|
-
}
|
|
366
|
-
let characteristicUUIDString: String
|
|
367
|
-
if characteristic != nil {
|
|
368
|
-
characteristicUUIDString = cbuuidToStringUppercase(characteristic!.uuid)
|
|
369
|
-
} else {
|
|
370
|
-
characteristicUUIDString = "UNKNOWN-CHARACTERISTIC"
|
|
371
|
-
}
|
|
372
|
-
return "\(prefix)|\(serviceUUIDString)|\(characteristicUUIDString)"
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
private func getKey(
|
|
376
|
-
_ prefix: String,
|
|
377
|
-
_ descriptor: CBDescriptor
|
|
378
|
-
) -> String {
|
|
379
|
-
let baseKey = self.getKey(prefix, descriptor.characteristic)
|
|
380
|
-
let descriptorUUIDString = cbuuidToStringUppercase(descriptor.uuid)
|
|
381
|
-
return "\(baseKey)|\(descriptorUUIDString)"
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
private func resolve(
|
|
385
|
-
_ key: String,
|
|
386
|
-
_ value: String
|
|
387
|
-
) {
|
|
388
|
-
let callback = self.callbackMap[key]
|
|
389
|
-
if callback != nil {
|
|
390
|
-
log("Resolve", key, value)
|
|
391
|
-
callback!(true, value)
|
|
392
|
-
self.callbackMap[key] = nil
|
|
393
|
-
self.timeoutMap[key]?.cancel()
|
|
394
|
-
self.timeoutMap[key] = nil
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
private func reject(
|
|
399
|
-
_ key: String,
|
|
400
|
-
_ value: String
|
|
401
|
-
) {
|
|
402
|
-
let callback = self.callbackMap[key]
|
|
403
|
-
if callback != nil {
|
|
404
|
-
log("Reject", key, value)
|
|
405
|
-
callback!(false, value)
|
|
406
|
-
self.callbackMap[key] = nil
|
|
407
|
-
self.timeoutMap[key]?.cancel()
|
|
408
|
-
self.timeoutMap[key] = nil
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
private func setTimeout(
|
|
413
|
-
_ key: String,
|
|
414
|
-
_ message: String,
|
|
415
|
-
_ timeout: Double
|
|
416
|
-
) {
|
|
417
|
-
let workItem = DispatchWorkItem {
|
|
418
|
-
self.reject(key, message)
|
|
419
|
-
}
|
|
420
|
-
self.timeoutMap[key] = workItem
|
|
421
|
-
DispatchQueue.main.asyncAfter(deadline: .now() + timeout, execute: workItem)
|
|
422
|
-
}
|
|
423
|
-
}
|
|
1
|
+
// swiftlint:disable type_body_length
|
|
2
|
+
import Foundation
|
|
3
|
+
import CoreBluetooth
|
|
4
|
+
|
|
5
|
+
class Device: NSObject, CBPeripheralDelegate {
|
|
6
|
+
typealias Callback = (_ success: Bool, _ value: String) -> Void
|
|
7
|
+
|
|
8
|
+
private var peripheral: CBPeripheral!
|
|
9
|
+
private var callbackMap = ThreadSafeDictionary<String, Callback>()
|
|
10
|
+
private var timeoutMap = [String: DispatchWorkItem]()
|
|
11
|
+
private var servicesCount = 0
|
|
12
|
+
private var servicesDiscovered = 0
|
|
13
|
+
private var characteristicsCount = 0
|
|
14
|
+
private var characteristicsDiscovered = 0
|
|
15
|
+
|
|
16
|
+
init(
|
|
17
|
+
_ peripheral: CBPeripheral
|
|
18
|
+
) {
|
|
19
|
+
super.init()
|
|
20
|
+
self.peripheral = peripheral
|
|
21
|
+
self.peripheral.delegate = self
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
func getName() -> String? {
|
|
25
|
+
return self.peripheral.name
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
func getId() -> String {
|
|
29
|
+
return self.peripheral.identifier.uuidString
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
func isConnected() -> Bool {
|
|
33
|
+
return self.peripheral.state == CBPeripheralState.connected
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
func getPeripheral() -> CBPeripheral {
|
|
37
|
+
return self.peripheral
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func setOnConnected(
|
|
41
|
+
_ connectionTimeout: Double,
|
|
42
|
+
_ callback: @escaping Callback
|
|
43
|
+
) {
|
|
44
|
+
let key = "connect"
|
|
45
|
+
self.callbackMap[key] = callback
|
|
46
|
+
self.setTimeout(key, "Connection timeout", connectionTimeout)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
func peripheral(
|
|
50
|
+
_ peripheral: CBPeripheral,
|
|
51
|
+
didDiscoverServices error: Error?
|
|
52
|
+
) {
|
|
53
|
+
log("didDiscoverServices")
|
|
54
|
+
if error != nil {
|
|
55
|
+
log("Error", error!.localizedDescription)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
self.servicesCount = peripheral.services?.count ?? 0
|
|
59
|
+
self.servicesDiscovered = 0
|
|
60
|
+
self.characteristicsCount = 0
|
|
61
|
+
self.characteristicsDiscovered = 0
|
|
62
|
+
for service in peripheral.services ?? [] {
|
|
63
|
+
peripheral.discoverCharacteristics(nil, for: service)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
func peripheral(
|
|
68
|
+
_ peripheral: CBPeripheral,
|
|
69
|
+
didDiscoverCharacteristicsFor service: CBService,
|
|
70
|
+
error: Error?
|
|
71
|
+
) {
|
|
72
|
+
self.servicesDiscovered += 1
|
|
73
|
+
log("didDiscoverCharacteristicsFor", self.servicesDiscovered, self.servicesCount)
|
|
74
|
+
self.characteristicsCount += service.characteristics?.count ?? 0
|
|
75
|
+
for characteristic in service.characteristics ?? [] {
|
|
76
|
+
peripheral.discoverDescriptors(for: characteristic)
|
|
77
|
+
}
|
|
78
|
+
// if the last service does not have characteristics, resolve the connect call now
|
|
79
|
+
if self.servicesDiscovered >= self.servicesCount && self.characteristicsDiscovered >= self.characteristicsCount {
|
|
80
|
+
self.resolve("connect", "Connection successful.")
|
|
81
|
+
self.resolve("discoverServices", "Services discovered.")
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func peripheral(
|
|
86
|
+
_ peripheral: CBPeripheral,
|
|
87
|
+
didDiscoverDescriptorsFor characteristic: CBCharacteristic,
|
|
88
|
+
error: Error?
|
|
89
|
+
) {
|
|
90
|
+
self.characteristicsDiscovered += 1
|
|
91
|
+
if self.servicesDiscovered >= self.servicesCount && self.characteristicsDiscovered >= self.characteristicsCount {
|
|
92
|
+
self.resolve("connect", "Connection successful.")
|
|
93
|
+
self.resolve("discoverServices", "Services discovered.")
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
func getServices() -> [CBService] {
|
|
98
|
+
return self.peripheral.services ?? []
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
func discoverServices(
|
|
102
|
+
_ timeout: Double,
|
|
103
|
+
_ callback: @escaping Callback
|
|
104
|
+
) {
|
|
105
|
+
let key = "discoverServices"
|
|
106
|
+
self.callbackMap[key] = callback
|
|
107
|
+
self.peripheral.discoverServices(nil)
|
|
108
|
+
self.setTimeout(key, "Service discovery timeout.", timeout)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
func getMtu() -> Int {
|
|
112
|
+
// maximumWriteValueLength is 3 bytes less than ATT MTU
|
|
113
|
+
return self.peripheral.maximumWriteValueLength(for: .withoutResponse) + 3
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
func readRssi(
|
|
117
|
+
_ timeout: Double,
|
|
118
|
+
_ callback: @escaping Callback
|
|
119
|
+
) {
|
|
120
|
+
let key = "readRssi"
|
|
121
|
+
self.callbackMap[key] = callback
|
|
122
|
+
log("Reading RSSI value")
|
|
123
|
+
self.peripheral.readRSSI()
|
|
124
|
+
self.setTimeout(key, "Reading RSSI timeout.", timeout)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func peripheral(
|
|
128
|
+
_ peripheral: CBPeripheral,
|
|
129
|
+
didReadRSSI RSSI: NSNumber,
|
|
130
|
+
error: Error?
|
|
131
|
+
) {
|
|
132
|
+
let key = "readRssi"
|
|
133
|
+
if error != nil {
|
|
134
|
+
self.reject(key, error!.localizedDescription)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
self.resolve(key, RSSI.stringValue)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private func getCharacteristic(
|
|
141
|
+
_ serviceUUID: CBUUID,
|
|
142
|
+
_ characteristicUUID: CBUUID
|
|
143
|
+
) -> CBCharacteristic? {
|
|
144
|
+
for service in peripheral.services ?? [] {
|
|
145
|
+
if service.uuid == serviceUUID {
|
|
146
|
+
for characteristic in service.characteristics ?? [] {
|
|
147
|
+
if characteristic.uuid == characteristicUUID {
|
|
148
|
+
return characteristic
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return nil
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private func getDescriptor(
|
|
157
|
+
_ serviceUUID: CBUUID,
|
|
158
|
+
_ characteristicUUID: CBUUID,
|
|
159
|
+
_ descriptorUUID: CBUUID
|
|
160
|
+
) -> CBDescriptor? {
|
|
161
|
+
guard let characteristic = self.getCharacteristic(serviceUUID, characteristicUUID) else {
|
|
162
|
+
return nil
|
|
163
|
+
}
|
|
164
|
+
for descriptor in characteristic.descriptors ?? [] {
|
|
165
|
+
if descriptor.uuid == descriptorUUID {
|
|
166
|
+
return descriptor
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return nil
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
func read(
|
|
173
|
+
_ serviceUUID: CBUUID,
|
|
174
|
+
_ characteristicUUID: CBUUID,
|
|
175
|
+
_ timeout: Double,
|
|
176
|
+
_ callback: @escaping Callback
|
|
177
|
+
) {
|
|
178
|
+
let key = "read|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)"
|
|
179
|
+
self.callbackMap[key] = callback
|
|
180
|
+
guard let characteristic = self.getCharacteristic(serviceUUID, characteristicUUID) else {
|
|
181
|
+
self.reject(key, "Characteristic not found.")
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
log("Reading value")
|
|
185
|
+
self.peripheral.readValue(for: characteristic)
|
|
186
|
+
self.setTimeout(key, "Read timeout.", timeout)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
func peripheral(
|
|
190
|
+
_ peripheral: CBPeripheral,
|
|
191
|
+
didUpdateValueFor characteristic: CBCharacteristic,
|
|
192
|
+
error: Error?
|
|
193
|
+
) {
|
|
194
|
+
let key = self.getKey("read", characteristic)
|
|
195
|
+
let notifyKey = self.getKey("notification", characteristic)
|
|
196
|
+
if error != nil {
|
|
197
|
+
self.reject(key, error!.localizedDescription)
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
if characteristic.value == nil {
|
|
201
|
+
self.reject(key, "Characteristic contains no value.")
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
// reading
|
|
205
|
+
let valueString = dataToString(characteristic.value!)
|
|
206
|
+
self.resolve(key, valueString)
|
|
207
|
+
|
|
208
|
+
// notifications
|
|
209
|
+
let callback = self.callbackMap[notifyKey]
|
|
210
|
+
if callback != nil {
|
|
211
|
+
callback!(true, valueString)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
func readDescriptor(
|
|
216
|
+
_ serviceUUID: CBUUID,
|
|
217
|
+
_ characteristicUUID: CBUUID,
|
|
218
|
+
_ descriptorUUID: CBUUID,
|
|
219
|
+
_ timeout: Double,
|
|
220
|
+
_ callback: @escaping Callback
|
|
221
|
+
) {
|
|
222
|
+
let key = "readDescriptor|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)|\(descriptorUUID.uuidString)"
|
|
223
|
+
self.callbackMap[key] = callback
|
|
224
|
+
guard let descriptor = self.getDescriptor(serviceUUID, characteristicUUID, descriptorUUID) else {
|
|
225
|
+
self.reject(key, "Descriptor not found.")
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
log("Reading descriptor value")
|
|
229
|
+
self.peripheral.readValue(for: descriptor)
|
|
230
|
+
self.setTimeout(key, "Read descriptor timeout.", timeout)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
func peripheral(
|
|
234
|
+
_ peripheral: CBPeripheral,
|
|
235
|
+
didUpdateValueFor descriptor: CBDescriptor,
|
|
236
|
+
error: Error?
|
|
237
|
+
) {
|
|
238
|
+
let key = self.getKey("readDescriptor", descriptor)
|
|
239
|
+
if error != nil {
|
|
240
|
+
self.reject(key, error!.localizedDescription)
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
if descriptor.value == nil {
|
|
244
|
+
self.reject(key, "Descriptor contains no value.")
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
let valueString = descriptorValueToString(descriptor.value!)
|
|
248
|
+
self.resolve(key, valueString)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
func write(
|
|
252
|
+
_ serviceUUID: CBUUID,
|
|
253
|
+
_ characteristicUUID: CBUUID,
|
|
254
|
+
_ value: String,
|
|
255
|
+
_ writeType: CBCharacteristicWriteType,
|
|
256
|
+
_ timeout: Double,
|
|
257
|
+
_ callback: @escaping Callback
|
|
258
|
+
) {
|
|
259
|
+
let key = "write|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)"
|
|
260
|
+
self.callbackMap[key] = callback
|
|
261
|
+
guard let characteristic = self.getCharacteristic(serviceUUID, characteristicUUID) else {
|
|
262
|
+
self.reject(key, "Characteristic not found.")
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
let data: Data = stringToData(value)
|
|
266
|
+
self.peripheral.writeValue(data, for: characteristic, type: writeType)
|
|
267
|
+
if writeType == CBCharacteristicWriteType.withResponse {
|
|
268
|
+
self.setTimeout(key, "Write timeout.", timeout)
|
|
269
|
+
} else {
|
|
270
|
+
self.resolve(key, "Successfully written value.")
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
func peripheral(
|
|
275
|
+
_ peripheral: CBPeripheral,
|
|
276
|
+
didWriteValueFor characteristic: CBCharacteristic,
|
|
277
|
+
error: Error?
|
|
278
|
+
) {
|
|
279
|
+
let key = self.getKey("write", characteristic)
|
|
280
|
+
if error != nil {
|
|
281
|
+
self.reject(key, error!.localizedDescription)
|
|
282
|
+
return
|
|
283
|
+
}
|
|
284
|
+
self.resolve(key, "Successfully written value.")
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
func writeDescriptor(
|
|
288
|
+
_ serviceUUID: CBUUID,
|
|
289
|
+
_ characteristicUUID: CBUUID,
|
|
290
|
+
_ descriptorUUID: CBUUID,
|
|
291
|
+
_ value: String,
|
|
292
|
+
_ timeout: Double,
|
|
293
|
+
_ callback: @escaping Callback
|
|
294
|
+
) {
|
|
295
|
+
let key = "writeDescriptor|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)|\(descriptorUUID.uuidString)"
|
|
296
|
+
self.callbackMap[key] = callback
|
|
297
|
+
guard let descriptor = self.getDescriptor(serviceUUID, characteristicUUID, descriptorUUID) else {
|
|
298
|
+
self.reject(key, "Descriptor not found.")
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
let data: Data = stringToData(value)
|
|
302
|
+
self.peripheral.writeValue(data, for: descriptor)
|
|
303
|
+
self.setTimeout(key, "Write descriptor timeout.", timeout)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
func peripheral(
|
|
307
|
+
_ peripheral: CBPeripheral,
|
|
308
|
+
didWriteValueFor descriptor: CBDescriptor,
|
|
309
|
+
error: Error?
|
|
310
|
+
) {
|
|
311
|
+
let key = self.getKey("writeDescriptor", descriptor)
|
|
312
|
+
if error != nil {
|
|
313
|
+
self.reject(key, error!.localizedDescription)
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
self.resolve(key, "Successfully written descriptor value.")
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
func setNotifications(
|
|
320
|
+
_ serviceUUID: CBUUID,
|
|
321
|
+
_ characteristicUUID: CBUUID,
|
|
322
|
+
_ enable: Bool,
|
|
323
|
+
_ notifyCallback: Callback?,
|
|
324
|
+
_ timeout: Double,
|
|
325
|
+
_ callback: @escaping Callback
|
|
326
|
+
) {
|
|
327
|
+
let key = "setNotifications|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)"
|
|
328
|
+
let notifyKey = "notification|\(serviceUUID.uuidString)|\(characteristicUUID.uuidString)"
|
|
329
|
+
self.callbackMap[key] = callback
|
|
330
|
+
if notifyCallback != nil {
|
|
331
|
+
self.callbackMap[notifyKey] = notifyCallback
|
|
332
|
+
}
|
|
333
|
+
guard let characteristic = self.getCharacteristic(serviceUUID, characteristicUUID) else {
|
|
334
|
+
self.reject(key, "Characteristic not found.")
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
log("Set notifications", enable)
|
|
338
|
+
self.peripheral.setNotifyValue(enable, for: characteristic)
|
|
339
|
+
self.setTimeout(key, "Set notifications timeout.", timeout)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
func peripheral(
|
|
343
|
+
_ peripheral: CBPeripheral,
|
|
344
|
+
didUpdateNotificationStateFor characteristic: CBCharacteristic,
|
|
345
|
+
error: Error?
|
|
346
|
+
) {
|
|
347
|
+
let key = self.getKey("setNotifications", characteristic)
|
|
348
|
+
if error != nil {
|
|
349
|
+
self.reject(key, error!.localizedDescription)
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
self.resolve(key, "Successfully set notifications.")
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private func getKey(
|
|
356
|
+
_ prefix: String,
|
|
357
|
+
_ characteristic: CBCharacteristic?
|
|
358
|
+
) -> String {
|
|
359
|
+
let serviceUUIDString: String
|
|
360
|
+
let service: CBService? = characteristic?.service
|
|
361
|
+
if service != nil {
|
|
362
|
+
serviceUUIDString = cbuuidToStringUppercase(service!.uuid)
|
|
363
|
+
} else {
|
|
364
|
+
serviceUUIDString = "UNKNOWN-SERVICE"
|
|
365
|
+
}
|
|
366
|
+
let characteristicUUIDString: String
|
|
367
|
+
if characteristic != nil {
|
|
368
|
+
characteristicUUIDString = cbuuidToStringUppercase(characteristic!.uuid)
|
|
369
|
+
} else {
|
|
370
|
+
characteristicUUIDString = "UNKNOWN-CHARACTERISTIC"
|
|
371
|
+
}
|
|
372
|
+
return "\(prefix)|\(serviceUUIDString)|\(characteristicUUIDString)"
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
private func getKey(
|
|
376
|
+
_ prefix: String,
|
|
377
|
+
_ descriptor: CBDescriptor
|
|
378
|
+
) -> String {
|
|
379
|
+
let baseKey = self.getKey(prefix, descriptor.characteristic)
|
|
380
|
+
let descriptorUUIDString = cbuuidToStringUppercase(descriptor.uuid)
|
|
381
|
+
return "\(baseKey)|\(descriptorUUIDString)"
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private func resolve(
|
|
385
|
+
_ key: String,
|
|
386
|
+
_ value: String
|
|
387
|
+
) {
|
|
388
|
+
let callback = self.callbackMap[key]
|
|
389
|
+
if callback != nil {
|
|
390
|
+
log("Resolve", key, value)
|
|
391
|
+
callback!(true, value)
|
|
392
|
+
self.callbackMap[key] = nil
|
|
393
|
+
self.timeoutMap[key]?.cancel()
|
|
394
|
+
self.timeoutMap[key] = nil
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private func reject(
|
|
399
|
+
_ key: String,
|
|
400
|
+
_ value: String
|
|
401
|
+
) {
|
|
402
|
+
let callback = self.callbackMap[key]
|
|
403
|
+
if callback != nil {
|
|
404
|
+
log("Reject", key, value)
|
|
405
|
+
callback!(false, value)
|
|
406
|
+
self.callbackMap[key] = nil
|
|
407
|
+
self.timeoutMap[key]?.cancel()
|
|
408
|
+
self.timeoutMap[key] = nil
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private func setTimeout(
|
|
413
|
+
_ key: String,
|
|
414
|
+
_ message: String,
|
|
415
|
+
_ timeout: Double
|
|
416
|
+
) {
|
|
417
|
+
let workItem = DispatchWorkItem {
|
|
418
|
+
self.reject(key, message)
|
|
419
|
+
}
|
|
420
|
+
self.timeoutMap[key] = workItem
|
|
421
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + timeout, execute: workItem)
|
|
422
|
+
}
|
|
423
|
+
}
|