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 init(peripheral: CBPeripheral, centralManager: CBCentralManager) { self.peripheral = peripheral self.centralManager = centralManager } func connect() async throws { let provisioners: [() -> any BeaconProvisioner] = [ { KBeaconProvisioner(peripheral: self.peripheral, centralManager: self.centralManager) }, { DXSmartProvisioner(peripheral: self.peripheral, centralManager: self.centralManager) }, { BlueCharmProvisioner(peripheral: self.peripheral, centralManager: self.centralManager) }, ] var lastError: Error = ProvisionError.connectionTimeout for makeProvisioner in provisioners { let provisioner = makeProvisioner() do { try await provisioner.connect() activeProvisioner = provisioner isConnected = true return } catch { provisioner.disconnect() lastError = error } } 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 } }