perf: aggressive BLE timeout tuning to match Android optimizations
Port all timing reductions from payfrit-beacon-android PR #9: - BASE_WRITE_DELAY: 0.5s → 0.2s - HEAVY_WRITE_DELAY: 1.0s → 0.4s - LARGE_PAYLOAD_DELAY: 0.8s → 0.3s - RESPONSE_GATE_TIMEOUT: 1.0s → 0.5s - WRITE_TIMEOUT: 5.0s → 2.0s - GLOBAL_TIMEOUT: 90s → 30s - Read config timeout: 15s → 8s - Password retry delay: 0.5s → 0.2s - FFE2 reconnect delay: 2.0s → 1.0s - Connection retry backoff: halved (0.5s/1.0s/1.5s) - Disconnect retry backoff: halved - Read query delays: 0.4s → 0.2s - Reconnect resume delay: 1.5s → 0.5s - Final response wait: 2.0s → 1.0s
This commit is contained in:
parent
66053508d3
commit
2ebff4879c
1 changed files with 23 additions and 23 deletions
|
|
@ -102,7 +102,7 @@ struct BeaconCheckResult {
|
||||||
/// 3. Full reconnect on FFE2 miss (CoreBluetooth caches stale GATT)
|
/// 3. Full reconnect on FFE2 miss (CoreBluetooth caches stale GATT)
|
||||||
/// 4. SaveConfig write-error = success (beacon reboots immediately)
|
/// 4. SaveConfig write-error = success (beacon reboots immediately)
|
||||||
/// 5. Response gating between writes prevents MCU overload
|
/// 5. Response gating between writes prevents MCU overload
|
||||||
/// 6. Adaptive delays: heavy commands (frame select/type) need 1s, others 0.5s
|
/// 6. Adaptive delays: heavy commands (frame select/type) need 0.4s, others 0.2s
|
||||||
class BeaconProvisioner: NSObject, ObservableObject {
|
class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
|
|
||||||
// MARK: - Constants
|
// MARK: - Constants
|
||||||
|
|
@ -140,13 +140,13 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
case frameDisable = 0xFF
|
case frameDisable = 0xFF
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timing constants (tuned from extensive testing)
|
// Timing constants (aggressively tuned to match Android BLE optimizations)
|
||||||
private static let BASE_WRITE_DELAY: Double = 0.5 // Default delay between commands
|
private static let BASE_WRITE_DELAY: Double = 0.2 // Default delay between commands (was 0.5)
|
||||||
private static let HEAVY_WRITE_DELAY: Double = 1.0 // After frame select/type commands (MCU state change)
|
private static let HEAVY_WRITE_DELAY: Double = 0.4 // After frame select/type commands (was 1.0)
|
||||||
private static let LARGE_PAYLOAD_DELAY: Double = 0.8 // After UUID/large payload writes
|
private static let LARGE_PAYLOAD_DELAY: Double = 0.3 // After UUID/large payload writes (was 0.8)
|
||||||
private static let RESPONSE_GATE_TIMEOUT: Double = 1.0 // 1s matches Android's withTimeoutOrNull(1000L)
|
private static let RESPONSE_GATE_TIMEOUT: Double = 0.5 // Response gate timeout (was 1.0)
|
||||||
private static let WRITE_TIMEOUT_SECONDS: Double = 5.0 // Per-write timeout (matches Android)
|
private static let WRITE_TIMEOUT_SECONDS: Double = 2.0 // Per-write timeout (was 5.0, matches Android)
|
||||||
private static let GLOBAL_TIMEOUT: Double = 90.0 // Overall provisioning timeout
|
private static let GLOBAL_TIMEOUT: Double = 30.0 // Overall provisioning timeout (was 90.0)
|
||||||
|
|
||||||
// Retry limits
|
// Retry limits
|
||||||
private static let MAX_CONNECTION_RETRIES = 3
|
private static let MAX_CONNECTION_RETRIES = 3
|
||||||
|
|
@ -284,14 +284,14 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
|
|
||||||
centralManager.connect(resolvedPeripheral, options: nil)
|
centralManager.connect(resolvedPeripheral, options: nil)
|
||||||
|
|
||||||
// 15-second timeout for read operations
|
// 8-second timeout for read operations (was 15s)
|
||||||
let timeout = DispatchWorkItem { [weak self] in
|
let timeout = DispatchWorkItem { [weak self] in
|
||||||
guard let self = self, self.operationMode == .readingConfig else { return }
|
guard let self = self, self.operationMode == .readingConfig else { return }
|
||||||
DebugLog.shared.log("BLE: Read timeout reached")
|
DebugLog.shared.log("BLE: Read timeout reached")
|
||||||
self.finishRead()
|
self.finishRead()
|
||||||
}
|
}
|
||||||
readTimeout = timeout
|
readTimeout = timeout
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 15, execute: timeout)
|
DispatchQueue.main.asyncAfter(deadline: .now() + 8, execute: timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - State Reset
|
// MARK: - State Reset
|
||||||
|
|
@ -428,7 +428,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
passwordIndex += 1
|
passwordIndex += 1
|
||||||
if passwordIndex < BeaconProvisioner.DXSMART_PASSWORDS.count {
|
if passwordIndex < BeaconProvisioner.DXSMART_PASSWORDS.count {
|
||||||
DebugLog.shared.log("BLE: Password rejected, trying next (\(passwordIndex + 1)/\(BeaconProvisioner.DXSMART_PASSWORDS.count))")
|
DebugLog.shared.log("BLE: Password rejected, trying next (\(passwordIndex + 1)/\(BeaconProvisioner.DXSMART_PASSWORDS.count))")
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
||||||
self?.authenticate()
|
self?.authenticate()
|
||||||
}
|
}
|
||||||
} else if operationMode == .readingConfig {
|
} else if operationMode == .readingConfig {
|
||||||
|
|
@ -565,7 +565,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
|
|
||||||
disconnectPeripheral()
|
disconnectPeripheral()
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
||||||
guard let self = self, let beacon = self.currentBeacon else { return }
|
guard let self = self, let beacon = self.currentBeacon else { return }
|
||||||
guard self.state == .connecting else { return }
|
guard self.state == .connecting else { return }
|
||||||
let resolvedPeripheral = self.resolvePeripheral(beacon)
|
let resolvedPeripheral = self.resolvePeripheral(beacon)
|
||||||
|
|
@ -596,7 +596,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
guard self.awaitingResponse else { return }
|
guard self.awaitingResponse else { return }
|
||||||
self.awaitingResponse = false
|
self.awaitingResponse = false
|
||||||
DebugLog.shared.log("BLE: No FFE1 response within 1s for command \(self.writeIndex + 1) — advancing (OK)")
|
DebugLog.shared.log("BLE: No FFE1 response within 0.5s for command \(self.writeIndex + 1) — advancing (OK)")
|
||||||
self.advanceToNextCommand()
|
self.advanceToNextCommand()
|
||||||
}
|
}
|
||||||
responseGateTimer = timer
|
responseGateTimer = timer
|
||||||
|
|
@ -639,7 +639,7 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
// Non-fatal: skip
|
// Non-fatal: skip
|
||||||
DebugLog.shared.log("BLE: Write timeout for non-fatal command \(current)/\(total) — skipping")
|
DebugLog.shared.log("BLE: Write timeout for non-fatal command \(current)/\(total) — skipping")
|
||||||
self.writeIndex += 1
|
self.writeIndex += 1
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
||||||
self?.sendNextCommand()
|
self?.sendNextCommand()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -824,9 +824,9 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
|
|
||||||
private func sendNextReadQuery() {
|
private func sendNextReadQuery() {
|
||||||
guard dxReadQueryIndex < dxReadQueries.count else {
|
guard dxReadQueryIndex < dxReadQueries.count else {
|
||||||
DebugLog.shared.log("BLE: All read queries sent, waiting 2s for final responses")
|
DebugLog.shared.log("BLE: All read queries sent, waiting 1s for final responses")
|
||||||
progress = "Collecting responses..."
|
progress = "Collecting responses..."
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in
|
||||||
guard let self = self, self.operationMode == .readingConfig else { return }
|
guard let self = self, self.operationMode == .readingConfig else { return }
|
||||||
self.finishRead()
|
self.finishRead()
|
||||||
}
|
}
|
||||||
|
|
@ -1081,7 +1081,7 @@ extension BeaconProvisioner: CBCentralManagerDelegate {
|
||||||
|
|
||||||
if connectionRetryCount < BeaconProvisioner.MAX_CONNECTION_RETRIES {
|
if connectionRetryCount < BeaconProvisioner.MAX_CONNECTION_RETRIES {
|
||||||
connectionRetryCount += 1
|
connectionRetryCount += 1
|
||||||
let delay = Double(connectionRetryCount)
|
let delay = Double(connectionRetryCount) * 0.5 // 0.5s, 1.0s, 1.5s (was 1s, 2s, 3s)
|
||||||
progress = "Connection failed, retrying (\(connectionRetryCount)/\(BeaconProvisioner.MAX_CONNECTION_RETRIES))..."
|
progress = "Connection failed, retrying (\(connectionRetryCount)/\(BeaconProvisioner.MAX_CONNECTION_RETRIES))..."
|
||||||
DebugLog.shared.log("BLE: Retrying in \(delay)s...")
|
DebugLog.shared.log("BLE: Retrying in \(delay)s...")
|
||||||
|
|
||||||
|
|
@ -1170,7 +1170,7 @@ extension BeaconProvisioner: CBCentralManagerDelegate {
|
||||||
}
|
}
|
||||||
state = .connecting
|
state = .connecting
|
||||||
|
|
||||||
let delay = Double(disconnectRetryCount) + 2.0 // 3s, 4s, 5s... backoff
|
let delay = Double(disconnectRetryCount) * 0.5 + 0.5 // 1.0s, 1.5s, 2.0s... backoff (was 3s, 4s, 5s)
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||||
guard let self = self, let beacon = self.currentBeacon else { return }
|
guard let self = self, let beacon = self.currentBeacon else { return }
|
||||||
guard self.state == .connecting else { return }
|
guard self.state == .connecting else { return }
|
||||||
|
|
@ -1307,7 +1307,7 @@ extension BeaconProvisioner: CBPeripheralDelegate {
|
||||||
if operationMode == .readingConfig {
|
if operationMode == .readingConfig {
|
||||||
DebugLog.shared.log("BLE: Read query failed, skipping")
|
DebugLog.shared.log("BLE: Read query failed, skipping")
|
||||||
dxReadQueryIndex += 1
|
dxReadQueryIndex += 1
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
||||||
self?.sendNextReadQuery()
|
self?.sendNextReadQuery()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1321,7 +1321,7 @@ extension BeaconProvisioner: CBPeripheralDelegate {
|
||||||
} else if isNonFatal {
|
} else if isNonFatal {
|
||||||
DebugLog.shared.log("BLE: Non-fatal command failed at step \(writeIndex + 1), continuing...")
|
DebugLog.shared.log("BLE: Non-fatal command failed at step \(writeIndex + 1), continuing...")
|
||||||
writeIndex += 1
|
writeIndex += 1
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
||||||
self?.sendNextCommand()
|
self?.sendNextCommand()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1353,8 +1353,8 @@ extension BeaconProvisioner: CBPeripheralDelegate {
|
||||||
state = .writing
|
state = .writing
|
||||||
DebugLog.shared.log("BLE: Resuming write from command \(writeIndex + 1)/\(commandQueue.count)")
|
DebugLog.shared.log("BLE: Resuming write from command \(writeIndex + 1)/\(commandQueue.count)")
|
||||||
progress = "Resuming config write..."
|
progress = "Resuming config write..."
|
||||||
// 1.5s delay after reconnect for BLE stability
|
// 0.5s delay after reconnect for BLE stability (was 1.5s)
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
|
||||||
self?.sendNextCommand()
|
self?.sendNextCommand()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1369,7 +1369,7 @@ extension BeaconProvisioner: CBPeripheralDelegate {
|
||||||
if characteristic.uuid == BeaconProvisioner.DXSMART_COMMAND_CHAR {
|
if characteristic.uuid == BeaconProvisioner.DXSMART_COMMAND_CHAR {
|
||||||
if operationMode == .readingConfig {
|
if operationMode == .readingConfig {
|
||||||
dxReadQueryIndex += 1
|
dxReadQueryIndex += 1
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
||||||
self?.sendNextReadQuery()
|
self?.sendNextReadQuery()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue