Merge remote-tracking branch 'origin/schwifty/fix-device-info-disconnect-retry'

This commit is contained in:
Schwifty 2026-03-21 23:37:26 +00:00
commit 07c5a22315

View file

@ -157,6 +157,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
private var dxSmartWriteIndex = 0
private var provisioningMacAddress: String?
private var awaitingDeviceInfoForProvisioning = false
private var skipDeviceInfoRead = false // set after disconnect during device info skip MAC read on reconnect
private var isTerminating = false // guards against re-entrant disconnect handling
// Read config mode
@ -215,6 +216,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
self.dxSmartWriteIndex = 0
self.provisioningMacAddress = nil
self.awaitingDeviceInfoForProvisioning = false
self.skipDeviceInfoRead = false
self.isTerminating = false
self.connectionRetryCount = 0
self.currentBeacon = beacon
@ -298,6 +300,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
dxSmartWriteIndex = 0
provisioningMacAddress = nil
awaitingDeviceInfoForProvisioning = false
skipDeviceInfoRead = false
isTerminating = false
connectionRetryCount = 0
currentBeacon = nil
@ -406,6 +409,14 @@ class BeaconProvisioner: NSObject, ObservableObject {
/// Read device info (MAC address) before writing config
private func dxSmartReadDeviceInfoBeforeWrite() {
// If we previously disconnected during device info read, skip it entirely
if skipDeviceInfoRead {
DebugLog.shared.log("BLE: Skipping device info read (reconnect after previous disconnect)")
skipDeviceInfoRead = false
dxSmartWriteConfig()
return
}
guard let commandChar = characteristics[BeaconProvisioner.DXSMART_COMMAND_CHAR] else {
DebugLog.shared.log("BLE: FFE2 not found, proceeding without MAC")
dxSmartWriteConfig()
@ -1012,16 +1023,43 @@ extension BeaconProvisioner: CBCentralManagerDelegate {
return
}
// Disconnect during device info read (post-auth, pre-write) beacon may have
// dropped the connection during the MAC address query. We authenticated but
// lost connection before writing config, so this is a failure but a known
// one with a clear retry path, not an unexplained "Unexpected disconnect".
// Disconnect during device info read (post-auth, pre-write) beacon dropped
// connection during the optional MAC address query. Instead of failing, reconnect
// and skip the device info step (MAC is nice-to-have, not required).
if state == .authenticating && awaitingDeviceInfoForProvisioning && dxSmartAuthenticated {
DebugLog.shared.log("BLE: Disconnect during device info read (post-auth) — connection lost before config write, failing with retry prompt")
awaitingDeviceInfoForProvisioning = false
// Connection lost can't write config without it, fail with specific message
fail("Disconnected after auth during device info read — please retry", code: .disconnected)
skipDeviceInfoRead = true // on reconnect, go straight to config write
if connectionRetryCount < BeaconProvisioner.MAX_CONNECTION_RETRIES {
connectionRetryCount += 1
let delay = Double(connectionRetryCount)
progress = "Reconnecting (skip MAC read)..."
DebugLog.shared.log("BLE: Disconnect during device info read — reconnecting (\(connectionRetryCount)/\(BeaconProvisioner.MAX_CONNECTION_RETRIES)), will skip MAC read")
// Reset BLE state for reconnect
dxSmartAuthenticated = false
dxSmartNotifySubscribed = false
dxSmartCommandQueue.removeAll()
dxSmartWriteIndex = 0
characteristics.removeAll()
responseBuffer.removeAll()
state = .connecting
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
} else {
DebugLog.shared.log("BLE: Disconnect during device info read — max retries exhausted")
fail("Disconnected while reading device information (retries exhausted)", code: .disconnected)
return
}
}
// All other disconnects are unexpected