Skip to content

Commit 1ab3d9e

Browse files
committed
Merge branch 'bt'
2 parents 0e65f73 + 4a14ddc commit 1ab3d9e

File tree

3 files changed

+70
-34
lines changed

3 files changed

+70
-34
lines changed

frontends/ios/BitBoxApp/BitBoxApp/BitBoxAppApp.swift

+3-3
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ class GoEnvironment: NSObject, MobileserverGoEnvironmentInterfaceProtocol, UIDoc
6868
return nil
6969
}
7070

71-
let productStr = bluetoothManager.productStr();
72-
if productStr == "" || productStr == "no connection" {
71+
let productInfo = bluetoothManager.parseProduct();
72+
guard let productInfo = productInfo else {
7373
// Not ready or explicitly not connected (waiting for the device to enter
7474
// firmware or bootloader)
7575
return nil
7676
}
77-
return BluetoothDeviceInfo(bluetoothManager: bluetoothManager)
77+
return BluetoothDeviceInfo(bluetoothManager: bluetoothManager, productInfo: productInfo)
7878
}
7979

8080
func nativeLocale() -> String {

frontends/ios/BitBoxApp/BitBoxApp/Bluetooth.swift

+65-31
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@
88
import CoreBluetooth
99
import Mobileserver
1010

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+
1122
struct State {
1223
var bluetoothAvailable: Bool
1324
var discoveredPeripherals: [UUID: PeripheralMetadata]
@@ -32,12 +43,14 @@ var pairedDeviceIdentifiers: Set<String> {
3243

3344
class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CBPeripheralDelegate {
3445
private var state: State = State(bluetoothAvailable: false, discoveredPeripherals: [:], connecting: false)
35-
46+
3647
var centralManager: CBCentralManager!
3748
var connectedPeripheral: CBPeripheral?
3849
var pWriter: CBCharacteristic?
3950
var pReader: CBCharacteristic?
4051
var pProduct: CBCharacteristic?
52+
53+
private var isPaired: Bool = false
4154

4255
// Peripherals in this set will not be auto-connected even if previously paired.
4356
// This is for failed connections to not enter an infinite connect loop.
@@ -55,7 +68,7 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
5568
}
5669

5770
func isConnected() -> Bool {
58-
return connectedPeripheral != nil && pReader != nil && pWriter != nil;
71+
return isPaired && connectedPeripheral != nil && pReader != nil && pWriter != nil;
5972
}
6073

6174
func connect(to peripheralID: UUID) {
@@ -82,20 +95,14 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
8295
func centralManagerDidUpdateState(_ central: CBCentralManager) {
8396
state.bluetoothAvailable = centralManager.state == .poweredOn
8497
updateBackendState()
85-
98+
8699
switch central.state {
87100
case .poweredOn:
88101
print("BLE: on")
89102
restartScan()
90103
case .poweredOff, .unauthorized, .unsupported, .resetting, .unknown:
91104
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()
99106
@unknown default:
100107
print("BLE: Unknown Bluetooth state")
101108
}
@@ -136,9 +143,6 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
136143
state.connecting = false
137144
updateBackendState()
138145

139-
// Add to paired devices
140-
pairedDeviceIdentifiers.insert(peripheral.identifier.uuidString)
141-
142146
connectedPeripheral = peripheral
143147
peripheral.delegate = self
144148
peripheral.discoverServices(nil)
@@ -229,24 +233,42 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
229233
// Signal the semaphore to unblock `readBlocking`
230234
semaphore.signal()
231235
}
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+
}
234244
// Invoke device manager to scan now, which will make it detect the device being connected
235245
// (or disconnected, in case the product string indicates that) now instead of waiting for
236246
// the next scan.
237247
MobileserverUsbUpdate()
238248
}
239249
}
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+
}
240267

241268
// This method gets called if the peripheral disconnects
242269
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
243270
print("BLE: peripheral disconnected")
244-
connectedPeripheral = nil;
245-
pReader = nil;
246-
pWriter = nil;
247-
pProduct = nil;
248-
249-
restartScan()
271+
handleDisconnect()
250272
}
251273

252274
func readBlocking(length: Int) -> Data? {
@@ -271,14 +293,24 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
271293
return data
272294
}
273295

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
277300
}
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
280313
}
281-
return String(data: value, encoding: .utf8) ?? ""
282314
}
283315

284316
// Encode the Bluetooth state as JSON so it can be sent to the backend-
@@ -335,9 +367,11 @@ class BluetoothManager: NSObject, ObservableObject, CBCentralManagerDelegate, CB
335367
// product, version, etc.
336368
class BluetoothDeviceInfo: NSObject, MobileserverGoDeviceInfoInterfaceProtocol {
337369
private let bluetoothManager: BluetoothManager
370+
private let productInfo: ProductInfo
338371

339-
init(bluetoothManager: BluetoothManager) {
372+
init(bluetoothManager: BluetoothManager, productInfo: ProductInfo) {
340373
self.bluetoothManager = bluetoothManager
374+
self.productInfo = productInfo
341375
super.init()
342376

343377
}
@@ -347,7 +381,7 @@ class BluetoothDeviceInfo: NSObject, MobileserverGoDeviceInfoInterfaceProtocol {
347381
return ""
348382
}
349383

350-
return connectedPeripheral.identifier.uuidString
384+
return connectedPeripheral.identifier.uuidString + "-" + productInfo.product
351385
}
352386

353387
func interface() -> Int {
@@ -360,7 +394,7 @@ class BluetoothDeviceInfo: NSObject, MobileserverGoDeviceInfoInterfaceProtocol {
360394

361395
func product() -> String {
362396
// TODO: return bluetoothManager.productStr() and have the backend identify and handle it
363-
return "BitBox02BTC"
397+
return productInfo.product
364398
}
365399

366400
func vendorID() -> Int {
@@ -372,7 +406,7 @@ class BluetoothDeviceInfo: NSObject, MobileserverGoDeviceInfoInterfaceProtocol {
372406
}
373407

374408
func serial() -> String {
375-
return "v9.21.0"
409+
return "v" + productInfo.version
376410
}
377411

378412
func usagePage() -> Int {

frontends/web/src/routes/device/no-device-connected.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import { useTranslation } from 'react-i18next';
1818
import type { TPagePropsWithSettingsTabs } from '../settings/types';
19+
import { Bluetooth } from '@/components/bluetooth/bluetooth';
1920
import { GuideWrapper, GuidedContent, Header, Main } from '@/components/layout';
2021
import { ContentWrapper } from '@/components/contentwrapper/contentwrapper';
2122
import { ViewContent, View } from '@/components/view/view';
@@ -56,6 +57,7 @@ export const NoDeviceConnected = ({
5657
<div className={styles.noDevice}>
5758
{t('deviceSettings.noDevice')}
5859
</div>
60+
<Bluetooth />
5961
</WithSettingsTabs>
6062
</ViewContent>
6163
</View>

0 commit comments

Comments
 (0)