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