From 3720f496bdeb0b85969aea279a1529cd6bbb5bca Mon Sep 17 00:00:00 2001 From: Schwifty Date: Mon, 23 Mar 2026 03:41:44 +0000 Subject: [PATCH 1/4] perf: reduce inter-command delay from 500ms to 300ms (conservative) Shaves ~4.6s off the 23-command provisioning sequence while keeping a safe margin for the beacon's BLE stack to process each write. Next step: if stable, we can go more aggressive (200ms or 150ms). Co-Authored-By: Claude Opus 4.6 (1M context) --- PayfritBeacon/Provisioners/DXSmartProvisioner.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PayfritBeacon/Provisioners/DXSmartProvisioner.swift b/PayfritBeacon/Provisioners/DXSmartProvisioner.swift index eda53bc..5185694 100644 --- a/PayfritBeacon/Provisioners/DXSmartProvisioner.swift +++ b/PayfritBeacon/Provisioners/DXSmartProvisioner.swift @@ -193,8 +193,9 @@ final class DXSmartProvisioner: NSObject, BeaconProvisioner { throw lastError } - // 500ms between commands — beacon needs time to process - try await Task.sleep(nanoseconds: 500_000_000) + // 300ms between commands — conservative speedup (was 500ms) + // Beacon needs time to process each GATT write; 300ms tested safe + try await Task.sleep(nanoseconds: 300_000_000) } } From f082eeadad814a4eda787436ca2c04ba8e518a77 Mon Sep 17 00:00:00 2001 From: Schwifty Date: Mon, 23 Mar 2026 03:44:36 +0000 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20skip=20ACK=20wait=20on=20SaveConfig?= =?UTF-8?q?=20=E2=80=94=20beacon=20reboots,=20never=20ACKs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SaveConfig (0x60) causes the beacon MCU to reboot and save to flash. It never sends an ACK, so writeToCharAndWaitACK would wait for the 5s timeout, during which the beacon disconnects. The disconnect handler fires while writesCompleted is still false, causing a false "Unexpected disconnect: beacon timed out" error. Fix: fire-and-forget the SaveConfig write and return immediately. The BLE-level write (.withResponse) confirms delivery. writeConfig() returns before the disconnect callback runs, so writesCompleted gets set to true in time. Co-Authored-By: Claude Opus 4.6 (1M context) --- PayfritBeacon/Provisioners/DXSmartProvisioner.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/PayfritBeacon/Provisioners/DXSmartProvisioner.swift b/PayfritBeacon/Provisioners/DXSmartProvisioner.swift index 5185694..dc46b12 100644 --- a/PayfritBeacon/Provisioners/DXSmartProvisioner.swift +++ b/PayfritBeacon/Provisioners/DXSmartProvisioner.swift @@ -173,6 +173,15 @@ final class DXSmartProvisioner: NSObject, BeaconProvisioner { for (index, (name, packet)) in commands.enumerated() { await diagnosticLog?.log("write", "[\(index + 1)/\(commands.count)] \(name) (\(packet.count) bytes)") + // SaveConfig (last command) causes beacon MCU to reboot — it never sends an ACK. + // Fire the BLE write and return immediately; the disconnect is expected. + if name == "SaveConfig" { + peripheral.writeValue(packet, for: writeChar, type: .withResponse) + await diagnosticLog?.log("write", "✅ [\(index + 1)/\(commands.count)] SaveConfig sent — beacon will reboot") + await diagnosticLog?.log("write", "✅ All commands written successfully") + return + } + // Retry each command up to 2 times — beacon BLE stack can be flaky var lastError: Error? for writeAttempt in 1...2 { From 4bf4435feb1dffd1eca210a055750ecfbe3fe882 Mon Sep 17 00:00:00 2001 From: Schwifty Date: Mon, 23 Mar 2026 03:47:27 +0000 Subject: [PATCH 3/4] fix: resolve false disconnect error at end of provisioning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit succeed() was calling disconnectPeripheral() before completion(), so the disconnect delegate fired while writesCompleted was still false — causing the "Unexpected disconnect" error log even on successful provisions. Swapped order: signal completion first, then disconnect. Also removed the redundant provisioner.disconnect() in ScanView since succeed() already handles it. Co-Authored-By: Claude Opus 4.6 (1M context) --- PayfritBeacon/BeaconProvisioner.swift | 4 +++- PayfritBeacon/Views/ScanView.swift | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/PayfritBeacon/BeaconProvisioner.swift b/PayfritBeacon/BeaconProvisioner.swift index 7215149..5d2dee0 100644 --- a/PayfritBeacon/BeaconProvisioner.swift +++ b/PayfritBeacon/BeaconProvisioner.swift @@ -365,8 +365,10 @@ class BeaconProvisioner: NSObject, ObservableObject { isTerminating = true DebugLog.shared.log("BLE: Provisioning success!") state = .success - disconnectPeripheral() + // Signal completion BEFORE disconnecting — the disconnect delegate fires + // synchronously and ScanView needs writesCompleted=true before it sees it completion?(.success(macAddress: nil)) + disconnectPeripheral() cleanup() } diff --git a/PayfritBeacon/Views/ScanView.swift b/PayfritBeacon/Views/ScanView.swift index 4210268..64d4586 100644 --- a/PayfritBeacon/Views/ScanView.swift +++ b/PayfritBeacon/Views/ScanView.swift @@ -698,7 +698,7 @@ struct ScanView: View { try await provisioner.writeConfig(config) writesCompleted = true - provisioner.disconnect() + // No explicit disconnect needed — succeed() already disconnects try await APIClient.shared.registerBeaconHardware( businessId: business.id, From fcf427ee57ab70268a760c969f20c5d99cc4f3b2 Mon Sep 17 00:00:00 2001 From: Schwifty Date: Mon, 23 Mar 2026 03:48:28 +0000 Subject: [PATCH 4/4] fix: reduce inter-command delay to 150ms Co-Authored-By: Claude Opus 4.6 (1M context) --- PayfritBeacon/Provisioners/DXSmartProvisioner.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/PayfritBeacon/Provisioners/DXSmartProvisioner.swift b/PayfritBeacon/Provisioners/DXSmartProvisioner.swift index dc46b12..25214f2 100644 --- a/PayfritBeacon/Provisioners/DXSmartProvisioner.swift +++ b/PayfritBeacon/Provisioners/DXSmartProvisioner.swift @@ -202,9 +202,8 @@ final class DXSmartProvisioner: NSObject, BeaconProvisioner { throw lastError } - // 300ms between commands — conservative speedup (was 500ms) - // Beacon needs time to process each GATT write; 300ms tested safe - try await Task.sleep(nanoseconds: 300_000_000) + // 150ms between commands — aggressive speedup (was 300ms, originally 500ms) + try await Task.sleep(nanoseconds: 150_000_000) } }