8
8
import CoreBluetooth
9
9
import Mobileserver
10
10
11
+ struct ProductInfo : Codable {
12
+ let product : String
13
+ let version : String
14
+
15
+ // map struct fields to json keys
16
+ enum CodingKeys : String , CodingKey {
17
+ case product = " p "
18
+ case version = " v "
19
+ }
20
+ }
21
+
11
22
struct State {
12
23
var bluetoothAvailable : Bool
13
24
var discoveredPeripherals : [ UUID : PeripheralMetadata ]
@@ -32,12 +43,14 @@ var pairedDeviceIdentifiers: Set<String> {
32
43
33
44
class BluetoothManager : NSObject , ObservableObject , CBCentralManagerDelegate , CBPeripheralDelegate {
34
45
private var state : State = State ( bluetoothAvailable: false , discoveredPeripherals: [ : ] , connecting: false )
35
-
46
+
36
47
var centralManager : CBCentralManager !
37
48
var connectedPeripheral : CBPeripheral ?
38
49
var pWriter : CBCharacteristic ?
39
50
var pReader : CBCharacteristic ?
40
51
var pProduct : CBCharacteristic ?
52
+
53
+ private var isPaired : Bool = false
41
54
42
55
// Peripherals in this set will not be auto-connected even if previously paired.
43
56
// This is for failed connections to not enter an infinite connect loop.
@@ -55,7 +68,7 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
55
68
}
56
69
57
70
func isConnected( ) -> Bool {
58
- return connectedPeripheral != nil && pReader != nil && pWriter != nil ;
71
+ return isPaired && connectedPeripheral != nil && pReader != nil && pWriter != nil ;
59
72
}
60
73
61
74
func connect( to peripheralID: UUID ) {
@@ -82,20 +95,14 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
82
95
func centralManagerDidUpdateState( _ central: CBCentralManager ) {
83
96
state. bluetoothAvailable = centralManager. state == . poweredOn
84
97
updateBackendState ( )
85
-
98
+
86
99
switch central. state {
87
100
case . poweredOn:
88
101
print ( " BLE: on " )
89
102
restartScan ( )
90
103
case . poweredOff, . unauthorized, . unsupported, . resetting, . unknown:
91
104
print ( " BLE: unavailable or not supported " )
92
- connectedPeripheral = nil
93
- pReader = nil
94
- pWriter = nil
95
- pProduct = nil
96
- state. discoveredPeripherals. removeAll ( )
97
- state. connecting = false
98
- updateBackendState ( )
105
+ handleDisconnect ( )
99
106
@unknown default :
100
107
print ( " BLE: Unknown Bluetooth state " )
101
108
}
@@ -136,9 +143,6 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
136
143
state. connecting = false
137
144
updateBackendState ( )
138
145
139
- // Add to paired devices
140
- pairedDeviceIdentifiers. insert ( peripheral. identifier. uuidString)
141
-
142
146
connectedPeripheral = peripheral
143
147
peripheral. delegate = self
144
148
peripheral. discoverServices ( nil )
@@ -229,24 +233,42 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
229
233
// Signal the semaphore to unblock `readBlocking`
230
234
semaphore. signal ( )
231
235
}
232
- if characteristic == pProduct, let val = characteristic. value {
233
- print ( " BLE: product changed: \( val) " )
236
+ if characteristic == pProduct {
237
+ print ( " BLE: product changed: \( String ( describing: parseProduct ( ) ) ) " )
238
+ // We can only read the product characteristic when paired.
239
+ if !isPaired {
240
+ isPaired = true
241
+ // Add to paired devices
242
+ pairedDeviceIdentifiers. insert ( peripheral. identifier. uuidString)
243
+ }
234
244
// Invoke device manager to scan now, which will make it detect the device being connected
235
245
// (or disconnected, in case the product string indicates that) now instead of waiting for
236
246
// the next scan.
237
247
MobileserverUsbUpdate ( )
238
248
}
239
249
}
250
+
251
+ func handleDisconnect( ) {
252
+ connectedPeripheral = nil
253
+ pReader = nil
254
+ pWriter = nil
255
+ pProduct = nil
256
+ state. discoveredPeripherals. removeAll ( )
257
+ state. connecting = false
258
+ isPaired = false
259
+ updateBackendState ( )
260
+
261
+ // Have the backend scan right away, which will make it detect that we disconnected.
262
+ // Otherwise there would be up to a second of delay (the backend device manager scan interval).
263
+ MobileserverUsbUpdate ( )
264
+
265
+ restartScan ( )
266
+ }
240
267
241
268
// This method gets called if the peripheral disconnects
242
269
func centralManager( _ central: CBCentralManager , didDisconnectPeripheral peripheral: CBPeripheral , error: Error ? ) {
243
270
print ( " BLE: peripheral disconnected " )
244
- connectedPeripheral = nil ;
245
- pReader = nil ;
246
- pWriter = nil ;
247
- pProduct = nil ;
248
-
249
- restartScan ( )
271
+ handleDisconnect ( )
250
272
}
251
273
252
274
func readBlocking( length: Int ) -> Data ? {
@@ -271,14 +293,24 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
271
293
return data
272
294
}
273
295
274
- func productStr( ) -> String {
275
- guard let pProduct = self . pProduct else {
276
- return " "
296
+ func parseProduct( ) -> ProductInfo ? {
297
+ guard let pProduct = self . pProduct,
298
+ let value = pProduct. value else {
299
+ return nil
277
300
}
278
- guard let value = pProduct. value else {
279
- return " "
301
+
302
+ if value. isEmpty {
303
+ return nil
304
+ }
305
+
306
+ do {
307
+ let decoder = JSONDecoder ( )
308
+ let productInfo = try decoder. decode ( ProductInfo . self, from: value)
309
+ return productInfo
310
+ } catch {
311
+ print ( " BLE: Failed to parse product JSON: \( error) " )
312
+ return nil
280
313
}
281
- return String ( data: value, encoding: . utf8) ?? " "
282
314
}
283
315
284
316
// Encode the Bluetooth state as JSON so it can be sent to the backend-
@@ -335,9 +367,11 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
335
367
// product, version, etc.
336
368
class BluetoothDeviceInfo : NSObject , MobileserverGoDeviceInfoInterfaceProtocol {
337
369
private let bluetoothManager : BluetoothManager
370
+ private let productInfo : ProductInfo
338
371
339
- init ( bluetoothManager: BluetoothManager ) {
372
+ init ( bluetoothManager: BluetoothManager , productInfo : ProductInfo ) {
340
373
self . bluetoothManager = bluetoothManager
374
+ self . productInfo = productInfo
341
375
super. init ( )
342
376
343
377
}
@@ -347,7 +381,7 @@ class BluetoothDeviceInfo: NSObject, MobileserverGoDeviceInfoInterfaceProtocol {
347
381
return " "
348
382
}
349
383
350
- return connectedPeripheral. identifier. uuidString
384
+ return connectedPeripheral. identifier. uuidString + " - " + productInfo . product
351
385
}
352
386
353
387
func interface( ) -> Int {
@@ -360,7 +394,7 @@ class BluetoothDeviceInfo: NSObject, MobileserverGoDeviceInfoInterfaceProtocol {
360
394
361
395
func product( ) -> String {
362
396
// TODO: return bluetoothManager.productStr() and have the backend identify and handle it
363
- return " BitBox02BTC "
397
+ return productInfo . product
364
398
}
365
399
366
400
func vendorID( ) -> Int {
@@ -372,7 +406,7 @@ class BluetoothDeviceInfo: NSObject, MobileserverGoDeviceInfoInterfaceProtocol {
372
406
}
373
407
374
408
func serial( ) -> String {
375
- return " v9.21.0 "
409
+ return " v " + productInfo . version
376
410
}
377
411
378
412
func usagePage( ) -> Int {
0 commit comments