fix: auto-reconnect on disconnect during device info read instead of failing
The beacon sometimes drops BLE connection during the optional MAC address query (0x30) after auth. Previously this failed with "Disconnected after auth during device info read". Now we reconnect and skip the MAC read on retry, going straight to config write. MAC is nice-to-have, not required. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
58be00cb38
commit
3d56a1e31d
1 changed files with 46 additions and 8 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue