fix: auto-connect to UWB_TAG on launch — no user action needed

Root cause: autoReconnect was false on init, so the
centralManagerDidUpdateState(.poweredOn) callback was a no-op.
User had to tap 'Scan' to set autoReconnect=true and start scanning.

Fixes:
- Set autoReconnect=true before creating CBCentralManager so the
  .poweredOn callback immediately starts scanning on every app launch
- Scan timeout (15s): on expiry, call reconnectAfterDelay() instead
  of staying idle — retries every 2s until TAG is found (handles
  TAG still booting after firmware flash)

Behaviour after this change:
  Launch → auto-scan within ~1s of Bluetooth ready
  TAG not found after 15s → retry after 2s, repeat indefinitely
  TAG disconnects → rescan after 2s
  TAG reboots/reflashes → found within one 15s scan window
  User taps Disconnect → autoReconnect=false, stops all retries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sl-ios 2026-04-06 20:01:47 -04:00
parent cd90d6dbee
commit 615dc405d0

View File

@ -42,6 +42,10 @@ final class BLEManager: NSObject, ObservableObject {
override init() { override init() {
super.init() super.init()
// autoReconnect=true before creating the central so that
// centralManagerDidUpdateState(.poweredOn) immediately starts scanning
// without any user action. The user can still tap Disconnect to stop.
autoReconnect = true
central = CBCentralManager(delegate: self, central = CBCentralManager(delegate: self,
queue: DispatchQueue(label: "ble.queue", qos: .utility)) queue: DispatchQueue(label: "ble.queue", qos: .utility))
} }
@ -95,12 +99,13 @@ final class BLEManager: NSObject, ObservableObject {
// We filter by device name prefix in didDiscover instead. // We filter by device name prefix in didDiscover instead.
central.scanForPeripherals(withServices: nil, central.scanForPeripherals(withServices: nil,
options: [CBCentralManagerScanOptionAllowDuplicatesKey: false]) options: [CBCentralManagerScanOptionAllowDuplicatesKey: false])
// Auto-stop after 15 s if nothing found // Stop after 15 s if nothing found, then retry after 5 s (TAG may be booting)
scanTimer?.invalidate() scanTimer?.invalidate()
scanTimer = Timer.scheduledTimer(withTimeInterval: 15, repeats: false) { [weak self] _ in scanTimer = Timer.scheduledTimer(withTimeInterval: 15, repeats: false) { [weak self] _ in
guard let self else { return } guard let self else { return }
self.stopScan() self.stopScan()
DispatchQueue.main.async { self.connectionState = .idle } DispatchQueue.main.async { self.connectionState = .idle }
self.reconnectAfterDelay()
} }
} }