Merge pull request 'fix: reconnect retry for unexpected BLE disconnects' (#8) from schwifty/reconnect-on-unexpected-disconnect into main

This commit is contained in:
payfrit 2026-03-22 00:17:30 +00:00
commit 34e8ea0bab

View file

@ -179,8 +179,10 @@ class BeaconProvisioner: NSObject, ObservableObject {
// Connection retry state // Connection retry state
private var connectionRetryCount = 0 private var connectionRetryCount = 0
private var deviceInfoRetryCount = 0 private var deviceInfoRetryCount = 0
private var disconnectRetryCount = 0
private static let MAX_CONNECTION_RETRIES = 3 private static let MAX_CONNECTION_RETRIES = 3
private static let MAX_DEVICE_INFO_RETRIES = 2 private static let MAX_DEVICE_INFO_RETRIES = 2
private static let MAX_DISCONNECT_RETRIES = 2
private var currentBeacon: DiscoveredBeacon? private var currentBeacon: DiscoveredBeacon?
override init() { override init() {
@ -222,6 +224,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
self.isTerminating = false self.isTerminating = false
self.connectionRetryCount = 0 self.connectionRetryCount = 0
self.deviceInfoRetryCount = 0 self.deviceInfoRetryCount = 0
self.disconnectRetryCount = 0
self.currentBeacon = beacon self.currentBeacon = beacon
state = .connecting state = .connecting
@ -270,6 +273,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
self.allDiscoveredServices.removeAll() self.allDiscoveredServices.removeAll()
self.connectionRetryCount = 0 self.connectionRetryCount = 0
self.deviceInfoRetryCount = 0 self.deviceInfoRetryCount = 0
self.disconnectRetryCount = 0
self.isTerminating = false self.isTerminating = false
self.currentBeacon = beacon self.currentBeacon = beacon
self.servicesToExplore.removeAll() self.servicesToExplore.removeAll()
@ -308,6 +312,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
isTerminating = false isTerminating = false
connectionRetryCount = 0 connectionRetryCount = 0
deviceInfoRetryCount = 0 deviceInfoRetryCount = 0
disconnectRetryCount = 0
currentBeacon = nil currentBeacon = nil
state = .idle state = .idle
progress = "" progress = ""
@ -888,6 +893,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
characteristics.removeAll() characteristics.removeAll()
connectionRetryCount = 0 connectionRetryCount = 0
deviceInfoRetryCount = 0 deviceInfoRetryCount = 0
disconnectRetryCount = 0
currentBeacon = nil currentBeacon = nil
operationMode = .provisioning operationMode = .provisioning
state = .idle state = .idle
@ -1019,6 +1025,7 @@ extension BeaconProvisioner: CBCentralManagerDelegate {
dxSmartNotifySubscribed = false dxSmartNotifySubscribed = false
dxSmartCommandQueue.removeAll() dxSmartCommandQueue.removeAll()
dxSmartWriteIndex = 0 dxSmartWriteIndex = 0
passwordIndex = 0
characteristics.removeAll() characteristics.removeAll()
responseBuffer.removeAll() responseBuffer.removeAll()
state = .connecting state = .connecting
@ -1034,8 +1041,36 @@ extension BeaconProvisioner: CBCentralManagerDelegate {
return return
} }
// All other disconnects are unexpected // Unexpected disconnect during auth or writing retry with full reconnect
DebugLog.shared.log("BLE: UNEXPECTED disconnect — state=\(state) writeIdx=\(dxSmartWriteIndex) queueCount=\(dxSmartCommandQueue.count) authenticated=\(dxSmartAuthenticated)") if (state == .authenticating || state == .writing) && disconnectRetryCount < BeaconProvisioner.MAX_DISCONNECT_RETRIES {
disconnectRetryCount += 1
DebugLog.shared.log("BLE: Disconnect during \(state) — reconnecting (attempt \(disconnectRetryCount)/\(BeaconProvisioner.MAX_DISCONNECT_RETRIES))")
progress = "Beacon disconnected, reconnecting (\(disconnectRetryCount)/\(BeaconProvisioner.MAX_DISCONNECT_RETRIES))..."
// Reset connection state for clean reconnect
dxSmartAuthenticated = false
dxSmartNotifySubscribed = false
dxSmartCommandQueue.removeAll()
dxSmartWriteIndex = 0
passwordIndex = 0
characteristics.removeAll()
responseBuffer.removeAll()
state = .connecting
let delay = Double(disconnectRetryCount) + 1.0 // 2s, 3s backoff
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
guard let self = self, let beacon = self.currentBeacon else { return }
guard self.state == .connecting else { return }
let resolvedPeripheral = self.resolvePeripheral(beacon)
self.peripheral = resolvedPeripheral
resolvedPeripheral.delegate = self
self.centralManager.connect(resolvedPeripheral, options: nil)
}
return
}
// All retries exhausted or disconnect in unexpected state fail
DebugLog.shared.log("BLE: UNEXPECTED disconnect — state=\(state) writeIdx=\(dxSmartWriteIndex) queueCount=\(dxSmartCommandQueue.count) authenticated=\(dxSmartAuthenticated) disconnectRetries=\(disconnectRetryCount)")
fail("Unexpected disconnect (state: \(state))", code: .disconnected) fail("Unexpected disconnect (state: \(state))", code: .disconnected)
} }
} }