Compare commits

..

5 commits

Author SHA1 Message Date
ce81a1a3d8 Merge branch 'schwifty/faster-provisioning' into main 2026-03-23 03:50:41 +00:00
fcf427ee57 fix: reduce inter-command delay to 150ms
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 03:48:28 +00:00
4bf4435feb fix: resolve false disconnect error at end of provisioning
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) <noreply@anthropic.com>
2026-03-23 03:47:27 +00:00
f082eeadad fix: skip ACK wait on SaveConfig — beacon reboots, never ACKs
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) <noreply@anthropic.com>
2026-03-23 03:44:36 +00:00
3720f496bd 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) <noreply@anthropic.com>
2026-03-23 03:41:44 +00:00
3 changed files with 15 additions and 4 deletions

View file

@ -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()
}

View file

@ -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 {
@ -193,8 +202,8 @@ final class DXSmartProvisioner: NSObject, BeaconProvisioner {
throw lastError
}
// 500ms between commands beacon needs time to process
try await Task.sleep(nanoseconds: 500_000_000)
// 150ms between commands aggressive speedup (was 300ms, originally 500ms)
try await Task.sleep(nanoseconds: 150_000_000)
}
}

View file

@ -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,