diff --git a/PayfritBeacon/BeaconProvisioner.swift b/PayfritBeacon/BeaconProvisioner.swift index 71a3e50..14d333e 100644 --- a/PayfritBeacon/BeaconProvisioner.swift +++ b/PayfritBeacon/BeaconProvisioner.swift @@ -413,37 +413,14 @@ class BeaconProvisioner: NSObject, ObservableObject { } /// Read device info (MAC address) before writing config + /// NOTE: Device info read (0x30 query) is SKIPPED during provisioning because DX-Smart + /// beacons frequently drop the BLE connection during this optional query, causing + /// provisioning to fail. The MAC address is nice-to-have but not required — the API + /// falls back to iBeacon UUID as hardware ID when MAC is unavailable. + /// Device info is still read in readConfig/check mode where it doesn't block provisioning. 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 { - DebugLog.shared.log("BLE: FFE2 not found, proceeding without MAC") - dxSmartWriteConfig() - return - } - - progress = "Reading device info..." - awaitingDeviceInfoForProvisioning = true - responseBuffer.removeAll() - - // Send device info query (0x30) - let packet = buildDXPacket(cmd: .deviceInfo, data: []) - DebugLog.shared.log("BLE: Sending device info query to get MAC address") - peripheral?.writeValue(packet, for: commandChar, type: .withResponse) - - // Timeout after 3 seconds - proceed with write even if no MAC - DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { [weak self] in - guard let self = self, self.awaitingDeviceInfoForProvisioning else { return } - DebugLog.shared.log("BLE: Device info timeout, proceeding without MAC") - self.awaitingDeviceInfoForProvisioning = false - self.dxSmartWriteConfig() - } + DebugLog.shared.log("BLE: Skipping device info read — proceeding directly to config write (MAC is optional)") + dxSmartWriteConfig() } /// Build the full command sequence and start writing @@ -1029,43 +1006,32 @@ extension BeaconProvisioner: CBCentralManagerDelegate { return } - // Disconnect during device info read (post-auth, pre-write) — beacon dropped - // connection during the optional MAC address query. Instead of failing, reconnect - // and skip the device info step (MAC is nice-to-have, not required). + // NOTE: Device info read is now skipped entirely during provisioning + // (see dxSmartReadDeviceInfoBeforeWrite). This guard is kept as a safety net + // in case device info is re-enabled in the future. if state == .authenticating && awaitingDeviceInfoForProvisioning && dxSmartAuthenticated { + DebugLog.shared.log("BLE: Disconnect during device info read — proceeding without MAC (device info is optional)") awaitingDeviceInfoForProvisioning = false - skipDeviceInfoRead = true // on reconnect, go straight to config write + skipDeviceInfoRead = true - if deviceInfoRetryCount < BeaconProvisioner.MAX_DEVICE_INFO_RETRIES { - deviceInfoRetryCount += 1 - let delay = Double(deviceInfoRetryCount) - progress = "Reconnecting (skip MAC read)..." - DebugLog.shared.log("BLE: Disconnect during device info read — reconnecting (\(deviceInfoRetryCount)/\(BeaconProvisioner.MAX_DEVICE_INFO_RETRIES)), will skip MAC read") + // Reconnect and skip device info on next attempt + dxSmartAuthenticated = false + dxSmartNotifySubscribed = false + dxSmartCommandQueue.removeAll() + dxSmartWriteIndex = 0 + characteristics.removeAll() + responseBuffer.removeAll() + state = .connecting - // 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 device info retries exhausted (\(deviceInfoRetryCount)/\(BeaconProvisioner.MAX_DEVICE_INFO_RETRIES))") - fail("Disconnected while reading device information (retries exhausted)", code: .disconnected) - return + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [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 } // All other disconnects are unexpected