diff --git a/PayfritBeacon/Provisioners/DXSmartProvisioner.swift b/PayfritBeacon/Provisioners/DXSmartProvisioner.swift index 02a5d95..9abe1ab 100644 --- a/PayfritBeacon/Provisioners/DXSmartProvisioner.swift +++ b/PayfritBeacon/Provisioners/DXSmartProvisioner.swift @@ -172,14 +172,30 @@ final class DXSmartProvisioner: NSObject, BeaconProvisioner { for (index, (name, packet)) in commands.enumerated() { await diagnosticLog?.log("write", "[\(index + 1)/\(commands.count)] \(name) (\(packet.count) bytes)") - do { - try await writeToCharAndWaitACK(writeChar, data: packet, label: name) - } catch { - await diagnosticLog?.log("write", "[\(index + 1)/\(commands.count)] \(name) FAILED: \(error.localizedDescription)", isError: true) - throw error + + // Retry each command up to 2 times — beacon BLE stack can be flaky after KBeacon fallback + var lastError: Error? + for writeAttempt in 1...2 { + do { + try await writeToCharAndWaitACK(writeChar, data: packet, label: name) + lastError = nil + break + } catch { + lastError = error + if writeAttempt == 1 { + await diagnosticLog?.log("write", "[\(index + 1)/\(commands.count)] \(name) retry after: \(error.localizedDescription)") + try await Task.sleep(nanoseconds: 500_000_000) // 500ms before retry + } + } } - // 200ms between commands (matches Android SDK timer interval) - try await Task.sleep(nanoseconds: 200_000_000) + if let lastError { + await diagnosticLog?.log("write", "[\(index + 1)/\(commands.count)] \(name) FAILED: \(lastError.localizedDescription)", isError: true) + throw lastError + } + + // 500ms between commands — beacon needs time to process, especially after + // prior KBeacon auth attempts that may have stressed the BLE stack + try await Task.sleep(nanoseconds: 500_000_000) } } @@ -320,7 +336,9 @@ final class DXSmartProvisioner: NSObject, BeaconProvisioner { // Step 2: Auth password — fire and forget if let authData = Self.defaultPassword.data(using: .utf8) { peripheral.writeValue(authData, for: ffe3, type: .withoutResponse) - try await Task.sleep(nanoseconds: 100_000_000) // 100ms settle + // 500ms settle after auth — beacon needs time to enter config mode, + // especially if BLE stack was stressed by prior provisioner attempts + try await Task.sleep(nanoseconds: 500_000_000) } } diff --git a/PayfritBeacon/Provisioners/FallbackProvisioner.swift b/PayfritBeacon/Provisioners/FallbackProvisioner.swift index 769bc75..d723831 100644 --- a/PayfritBeacon/Provisioners/FallbackProvisioner.swift +++ b/PayfritBeacon/Provisioners/FallbackProvisioner.swift @@ -56,6 +56,12 @@ final class FallbackProvisioner: BeaconProvisioner { 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) + } } } diff --git a/PayfritBeacon/Provisioners/KBeaconProvisioner.swift b/PayfritBeacon/Provisioners/KBeaconProvisioner.swift index 7f693ee..242a2fc 100644 --- a/PayfritBeacon/Provisioners/KBeaconProvisioner.swift +++ b/PayfritBeacon/Provisioners/KBeaconProvisioner.swift @@ -24,12 +24,14 @@ final class KBeaconProvisioner: NSObject, BeaconProvisioner { } // MARK: - Known passwords (tried in order, matching Android) + // Known KBeacon default passwords (tried in order) + // Note: password 5 was previously a duplicate of password 3 — replaced with "000000" (6-char variant) private static let passwords: [Data] = [ - "kd1234".data(using: .utf8)!, - Data(repeating: 0, count: 16), - Data([0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x30,0x31,0x32,0x33,0x34,0x35,0x36]), - "0000000000000000".data(using: .utf8)!, - "1234567890123456".data(using: .utf8)! + "kd1234".data(using: .utf8)!, // KBeacon factory default + Data(repeating: 0, count: 16), // Binary zeros (16 bytes) + Data([0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x30,0x31,0x32,0x33,0x34,0x35,0x36]), // "1234567890123456" + "0000000000000000".data(using: .utf8)!, // ASCII zeros (16 bytes) + "000000".data(using: .utf8)!, // Short zero default (6 bytes) ] // MARK: - State diff --git a/PayfritBeacon/Services/APIClient.swift b/PayfritBeacon/Services/APIClient.swift index d5323c0..7bea61e 100644 --- a/PayfritBeacon/Services/APIClient.swift +++ b/PayfritBeacon/Services/APIClient.swift @@ -201,7 +201,10 @@ actor APIClient { guard resp.OK else { throw APIError.serverError(resp.MESSAGE ?? resp.ERROR ?? "Failed to allocate minor") } - return resp.BeaconMinor ?? 0 + guard let minor = resp.BeaconMinor, minor > 0 else { + throw APIError.serverError("API returned invalid minor value: \(resp.BeaconMinor ?? 0). Service point may not be configured correctly.") + } + return minor } /// API returns: { "OK": true, "BeaconHardwareID": 42, ... }