@@ -19,15 +19,25 @@ struct ProductInfo: Codable {
19
19
}
20
20
}
21
21
22
+ enum ConnectionState : String , Codable {
23
+ case discovered
24
+ // from the moment we try to connect until after we are paired (or until either step fails).
25
+ case connecting
26
+ case connected
27
+ // something went wrong, see connectionError.
28
+ case error
29
+ }
30
+
22
31
struct State {
23
32
var bluetoothAvailable : Bool
33
+ var scanning : Bool
24
34
var discoveredPeripherals : [ UUID : PeripheralMetadata ]
25
- var connecting : Bool
26
35
}
27
36
28
37
struct PeripheralMetadata {
29
38
let peripheral : CBPeripheral
30
39
let discoveredDate : Date
40
+ var connectionState : ConnectionState
31
41
var connectionError : String ? = nil
32
42
}
33
43
@@ -42,14 +52,18 @@ var pairedDeviceIdentifiers: Set<String> {
42
52
}
43
53
44
54
class BluetoothManager : NSObject , ObservableObject , CBCentralManagerDelegate , CBPeripheralDelegate {
45
- private var state : State = State ( bluetoothAvailable: false , discoveredPeripherals: [ : ] , connecting: false )
55
+ private var state : State = State (
56
+ bluetoothAvailable: false ,
57
+ scanning: false ,
58
+ discoveredPeripherals: [ : ]
59
+ )
46
60
47
61
var centralManager : CBCentralManager !
48
62
var connectedPeripheral : CBPeripheral ?
49
63
var pWriter : CBCharacteristic ?
50
64
var pReader : CBCharacteristic ?
51
65
var pProduct : CBCharacteristic ?
52
-
66
+
53
67
private var isPaired : Bool = false
54
68
55
69
// Peripherals in this set will not be auto-connected even if previously paired.
@@ -72,10 +86,12 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
72
86
}
73
87
74
88
func connect( to peripheralID: UUID ) {
75
- guard let metadata = state. discoveredPeripherals [ peripheralID] else { return }
89
+ guard var metadata = state. discoveredPeripherals [ peripheralID] else { return }
76
90
centralManager. stopScan ( )
77
- state. discoveredPeripherals [ peripheralID] ? . connectionError = nil
78
- state. connecting = true
91
+ metadata. connectionError = nil
92
+ metadata. connectionState = . connecting
93
+ state. discoveredPeripherals [ peripheralID] = metadata
94
+ state. scanning = false
79
95
updateBackendState ( )
80
96
centralManager. connect ( metadata. peripheral, options: nil )
81
97
}
@@ -85,6 +101,7 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
85
101
!centralManager. isScanning,
86
102
connectedPeripheral == nil else { return }
87
103
state. discoveredPeripherals. removeAll ( )
104
+ state. scanning = true
88
105
updateBackendState ( )
89
106
centralManager. scanForPeripherals (
90
107
withServices: [ CBUUID ( string: " e1511a45-f3db-44c0-82b8-6c880790d1f1 " ) ] ,
@@ -113,7 +130,8 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
113
130
if state. discoveredPeripherals [ identifier] == nil {
114
131
state. discoveredPeripherals [ identifier] = PeripheralMetadata (
115
132
peripheral: peripheral,
116
- discoveredDate: Date ( )
133
+ discoveredDate: Date ( ) ,
134
+ connectionState: . discovered
117
135
)
118
136
print ( " BLE: discovered \( peripheral. name ?? " unknown device " ) " )
119
137
if let data = advertisementData [ " kCBAdvDataManufacturerData " ] as? Data {
@@ -140,8 +158,6 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
140
158
print ( " BLE: Connected to \( peripheral. name ?? " unknown device " ) " )
141
159
142
160
state. discoveredPeripherals [ peripheral. identifier] ? . connectionError = nil
143
- state. connecting = false
144
- updateBackendState ( )
145
161
146
162
connectedPeripheral = peripheral
147
163
peripheral. delegate = self
@@ -150,11 +166,12 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
150
166
151
167
func centralManager( _ central: CBCentralManager , didFailToConnect peripheral: CBPeripheral , error: Error ? ) {
152
168
let errorMessage = error? . localizedDescription ?? " unknown error "
169
+ state. discoveredPeripherals [ peripheral. identifier] ? . connectionState = . error
153
170
state. discoveredPeripherals [ peripheral. identifier] ? . connectionError = errorMessage
154
- state. connecting = false
155
- dontAutoConnectSet. insert ( peripheral. identifier)
156
171
updateBackendState ( )
172
+ dontAutoConnectSet. insert ( peripheral. identifier)
157
173
print ( " BLE: connection failed to \( peripheral. name ?? " unknown device " ) : \( errorMessage) " )
174
+ restartScan ( )
158
175
}
159
176
160
177
func peripheral( _ peripheral: CBPeripheral , didDiscoverServices error: Error ? ) {
@@ -241,27 +258,28 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
241
258
// Add to paired devices
242
259
pairedDeviceIdentifiers. insert ( peripheral. identifier. uuidString)
243
260
}
261
+ state. discoveredPeripherals [ peripheral. identifier] ? . connectionState = . connected
262
+ updateBackendState ( )
244
263
// Invoke device manager to scan now, which will make it detect the device being connected
245
264
// (or disconnected, in case the product string indicates that) now instead of waiting for
246
265
// the next scan.
247
266
MobileserverUsbUpdate ( )
248
267
}
249
268
}
250
-
269
+
251
270
func handleDisconnect( ) {
252
271
connectedPeripheral = nil
253
272
pReader = nil
254
273
pWriter = nil
255
274
pProduct = nil
256
275
state. discoveredPeripherals. removeAll ( )
257
- state. connecting = false
258
- isPaired = false
276
+ isPaired = false
259
277
updateBackendState ( )
260
-
278
+
261
279
// Have the backend scan right away, which will make it detect that we disconnected.
262
280
// Otherwise there would be up to a second of delay (the backend device manager scan interval).
263
281
MobileserverUsbUpdate ( )
264
-
282
+
265
283
restartScan ( )
266
284
}
267
285
@@ -298,7 +316,7 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
298
316
let value = pProduct. value else {
299
317
return nil
300
318
}
301
-
319
+
302
320
if value. isEmpty {
303
321
return nil
304
322
}
@@ -319,25 +337,31 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
319
337
struct PeripheralJSON : Codable {
320
338
let identifier : String
321
339
let name : String
340
+ let connectionState : ConnectionState
322
341
let connectionError : String ?
323
342
}
324
343
325
344
struct StateJSON : Codable {
326
345
let bluetoothAvailable : Bool
346
+ let scanning : Bool
327
347
let peripherals : [ PeripheralJSON ]
328
- let connecting : Bool
329
348
}
330
349
331
350
// Convert discoveredPeripherals to the JSON structure
332
351
let peripherals = Array ( state. discoveredPeripherals. values) . map { metadata in
333
352
PeripheralJSON (
334
353
identifier: metadata. peripheral. identifier. uuidString,
335
354
name: metadata. peripheral. name ?? " BitBox " ,
355
+ connectionState: metadata. connectionState,
336
356
connectionError: metadata. connectionError
337
357
)
338
358
}
339
359
340
- let state = StateJSON ( bluetoothAvailable: state. bluetoothAvailable, peripherals: peripherals, connecting: state. connecting)
360
+ let state = StateJSON (
361
+ bluetoothAvailable: state. bluetoothAvailable,
362
+ scanning: state. scanning,
363
+ peripherals: peripherals
364
+ )
341
365
342
366
do {
343
367
let encoder = JSONEncoder ( )
0 commit comments