diff --git a/PayfritBeacon/BeaconProvisioner.swift b/PayfritBeacon/BeaconProvisioner.swift index b0906a6..d7d4eb4 100644 --- a/PayfritBeacon/BeaconProvisioner.swift +++ b/PayfritBeacon/BeaconProvisioner.swift @@ -190,6 +190,8 @@ class BeaconProvisioner: NSObject, ObservableObject { // If a write callback doesn't come back in time, we retry or fail gracefully // instead of hanging until the 30s global timeout private var writeTimeoutTimer: DispatchWorkItem? + private var charRediscoveryCount = 0 + private static let MAX_CHAR_REDISCOVERY = 2 private static let WRITE_TIMEOUT_SECONDS: Double = 5.0 private var writeRetryCount = 0 private static let MAX_WRITE_RETRIES = 1 // Retry once per command before failing @@ -336,6 +338,7 @@ class BeaconProvisioner: NSObject, ObservableObject { deviceInfoRetryCount = 0 disconnectRetryCount = 0 writeRetryCount = 0 + charRediscoveryCount = 0 currentBeacon = nil state = .idle progress = "" @@ -571,10 +574,29 @@ class BeaconProvisioner: NSObject, ObservableObject { progress = "Writing config (\(current)/\(total))..." guard let commandChar = characteristics[BeaconProvisioner.DXSMART_COMMAND_CHAR] else { - fail("DX-Smart command characteristic (FFE2) not found", code: .serviceNotFound) + // After disconnect+reconnect, characteristic discovery may return incomplete results. + // Re-discover characteristics instead of hard-failing. + if charRediscoveryCount < BeaconProvisioner.MAX_CHAR_REDISCOVERY, let service = configService { + charRediscoveryCount += 1 + DebugLog.shared.log("BLE: FFE2 missing — re-discovering characteristics (attempt \(charRediscoveryCount)/\(BeaconProvisioner.MAX_CHAR_REDISCOVERY))") + progress = "Re-discovering characteristics..." + state = .discoveringServices + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in + self?.peripheral?.discoverCharacteristics([ + BeaconProvisioner.DXSMART_NOTIFY_CHAR, + BeaconProvisioner.DXSMART_COMMAND_CHAR, + BeaconProvisioner.DXSMART_PASSWORD_CHAR + ], for: service) + } + } else { + fail("DX-Smart command characteristic (FFE2) not found after \(charRediscoveryCount) rediscovery attempts", code: .serviceNotFound) + } return } + // Reset rediscovery counter on successful characteristic access + charRediscoveryCount = 0 + DebugLog.shared.log("BLE: Writing command \(current)/\(total): \(packet.map { String(format: "%02X", $0) }.joined(separator: " "))") writeRetryCount = 0 scheduleWriteTimeout() @@ -1294,6 +1316,13 @@ extension BeaconProvisioner: CBPeripheralDelegate { if operationMode == .readingConfig { // Continue exploring next service exploreNextService() + } else if charRediscoveryCount > 0 && dxSmartAuthenticated && !dxSmartCommandQueue.isEmpty { + // Rediscovery during active write — resume writing directly (already authenticated) + DebugLog.shared.log("BLE: Characteristics re-discovered after FFE2 miss — resuming write") + state = .writing + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in + self?.dxSmartSendNextCommand() + } } else { // Provisioning: DX-Smart auth flow dxSmartStartAuth()