1. Minor allocation: reject minor=0 from API instead of silently using it. API returning null/0 means the service point isn't configured right. 2. DXSmart write reliability: - Add per-command retry (1 retry with 500ms backoff) - Increase inter-command delay from 200ms to 500ms - Increase post-auth settle from 100ms to 500ms - Add 2s cooldown in FallbackProvisioner between provisioner attempts The beacon's BLE stack gets hammered by KBeacon's 15 failed auth attempts before DXSmart even gets a chance. These timings give it breathing room. 3. KBeacon passwords: password 5 was a duplicate of password 3 (both "1234567890123456"). Replaced with "000000" (6-char variant). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
83 lines
3.1 KiB
Swift
83 lines
3.1 KiB
Swift
import Foundation
|
|
import CoreBluetooth
|
|
|
|
/// Tries KBeacon → DXSmart → BlueCharm in sequence for unknown beacon types.
|
|
/// Matches Android's fallback behavior when beacon type can't be determined.
|
|
final class FallbackProvisioner: BeaconProvisioner {
|
|
|
|
private let peripheral: CBPeripheral
|
|
private let centralManager: CBCentralManager
|
|
private var activeProvisioner: (any BeaconProvisioner)?
|
|
|
|
private(set) var isConnected: Bool = false
|
|
var diagnosticLog: ProvisionLog?
|
|
var bleManager: BLEManager?
|
|
|
|
init(peripheral: CBPeripheral, centralManager: CBCentralManager) {
|
|
self.peripheral = peripheral
|
|
self.centralManager = centralManager
|
|
}
|
|
|
|
func connect() async throws {
|
|
let provisioners: [() -> any BeaconProvisioner] = [
|
|
{ [self] in
|
|
var p = KBeaconProvisioner(peripheral: peripheral, centralManager: centralManager)
|
|
p.diagnosticLog = diagnosticLog
|
|
p.bleManager = bleManager
|
|
return p
|
|
},
|
|
{ [self] in
|
|
var p = DXSmartProvisioner(peripheral: peripheral, centralManager: centralManager)
|
|
p.diagnosticLog = diagnosticLog
|
|
p.bleManager = bleManager
|
|
return p
|
|
},
|
|
{ [self] in
|
|
var p = BlueCharmProvisioner(peripheral: peripheral, centralManager: centralManager)
|
|
p.diagnosticLog = diagnosticLog
|
|
p.bleManager = bleManager
|
|
return p
|
|
},
|
|
]
|
|
|
|
let typeNames = ["KBeacon", "DXSmart", "BlueCharm"]
|
|
var lastError: Error = ProvisionError.connectionTimeout
|
|
|
|
for (index, makeProvisioner) in provisioners.enumerated() {
|
|
await diagnosticLog?.log("fallback", "Trying \(typeNames[index]) provisioner…")
|
|
let provisioner = makeProvisioner()
|
|
do {
|
|
try await provisioner.connect()
|
|
activeProvisioner = provisioner
|
|
isConnected = true
|
|
await diagnosticLog?.log("fallback", "\(typeNames[index]) connected successfully")
|
|
return
|
|
} catch {
|
|
provisioner.disconnect()
|
|
lastError = error
|
|
await diagnosticLog?.log("fallback", "\(typeNames[index]) failed: \(error.localizedDescription)", isError: true)
|
|
// 2s cooldown between provisioner attempts — let the beacon's BLE stack recover
|
|
// from failed auth/connection before the next provisioner hammers it
|
|
if index < provisioners.count - 1 {
|
|
await diagnosticLog?.log("fallback", "Cooling down 2s before next provisioner…")
|
|
try? await Task.sleep(nanoseconds: 2_000_000_000)
|
|
}
|
|
}
|
|
}
|
|
|
|
throw lastError
|
|
}
|
|
|
|
func writeConfig(_ config: BeaconConfig) async throws {
|
|
guard let provisioner = activeProvisioner else {
|
|
throw ProvisionError.notConnected
|
|
}
|
|
try await provisioner.writeConfig(config)
|
|
}
|
|
|
|
func disconnect() {
|
|
activeProvisioner?.disconnect()
|
|
activeProvisioner = nil
|
|
isConnected = false
|
|
}
|
|
}
|