Compare commits
No commits in common. "cabb2825d875fed098c7b1065af07c3c53b53744" and "28aefd1bdf69837309792f399c3e96181289aeb4" have entirely different histories.
cabb2825d8
...
28aefd1bdf
1 changed files with 2 additions and 77 deletions
|
|
@ -185,14 +185,6 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
private static let MAX_DISCONNECT_RETRIES = 2
|
private static let MAX_DISCONNECT_RETRIES = 2
|
||||||
private var currentBeacon: DiscoveredBeacon?
|
private var currentBeacon: DiscoveredBeacon?
|
||||||
|
|
||||||
// Per-write timeout (matches Android's 5-second per-write timeout)
|
|
||||||
// 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 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
|
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
centralManager = CBCentralManager(delegate: self, queue: .main)
|
centralManager = CBCentralManager(delegate: self, queue: .main)
|
||||||
|
|
@ -233,7 +225,6 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
self.connectionRetryCount = 0
|
self.connectionRetryCount = 0
|
||||||
self.deviceInfoRetryCount = 0
|
self.deviceInfoRetryCount = 0
|
||||||
self.disconnectRetryCount = 0
|
self.disconnectRetryCount = 0
|
||||||
self.writeRetryCount = 0
|
|
||||||
self.currentBeacon = beacon
|
self.currentBeacon = beacon
|
||||||
|
|
||||||
state = .connecting
|
state = .connecting
|
||||||
|
|
@ -305,7 +296,6 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
// MARK: - Cleanup
|
// MARK: - Cleanup
|
||||||
|
|
||||||
private func cleanup() {
|
private func cleanup() {
|
||||||
cancelWriteTimeout()
|
|
||||||
peripheral = nil
|
peripheral = nil
|
||||||
config = nil
|
config = nil
|
||||||
completion = nil
|
completion = nil
|
||||||
|
|
@ -323,7 +313,6 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
connectionRetryCount = 0
|
connectionRetryCount = 0
|
||||||
deviceInfoRetryCount = 0
|
deviceInfoRetryCount = 0
|
||||||
disconnectRetryCount = 0
|
disconnectRetryCount = 0
|
||||||
writeRetryCount = 0
|
|
||||||
currentBeacon = nil
|
currentBeacon = nil
|
||||||
state = .idle
|
state = .idle
|
||||||
progress = ""
|
progress = ""
|
||||||
|
|
@ -546,7 +535,6 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
/// Send the next command in the DX-Smart queue
|
/// Send the next command in the DX-Smart queue
|
||||||
private func dxSmartSendNextCommand() {
|
private func dxSmartSendNextCommand() {
|
||||||
guard dxSmartWriteIndex < dxSmartCommandQueue.count else {
|
guard dxSmartWriteIndex < dxSmartCommandQueue.count else {
|
||||||
cancelWriteTimeout()
|
|
||||||
DebugLog.shared.log("BLE: All DX-Smart commands written!")
|
DebugLog.shared.log("BLE: All DX-Smart commands written!")
|
||||||
progress = "Configuration saved!"
|
progress = "Configuration saved!"
|
||||||
succeed()
|
succeed()
|
||||||
|
|
@ -564,61 +552,9 @@ class BeaconProvisioner: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugLog.shared.log("BLE: Writing command \(current)/\(total): \(packet.map { String(format: "%02X", $0) }.joined(separator: " "))")
|
DebugLog.shared.log("BLE: Writing command \(current)/\(total): \(packet.map { String(format: "%02X", $0) }.joined(separator: " "))")
|
||||||
writeRetryCount = 0
|
|
||||||
scheduleWriteTimeout()
|
|
||||||
peripheral?.writeValue(packet, for: commandChar, type: .withResponse)
|
peripheral?.writeValue(packet, for: commandChar, type: .withResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Schedule a per-write timeout — if the write callback doesn't come back
|
|
||||||
/// within WRITE_TIMEOUT_SECONDS, retry the write once or skip if non-fatal.
|
|
||||||
/// This matches Android's 5-second per-write timeout via withTimeoutOrNull(5000L).
|
|
||||||
private func scheduleWriteTimeout() {
|
|
||||||
cancelWriteTimeout()
|
|
||||||
let timer = DispatchWorkItem { [weak self] in
|
|
||||||
guard let self = self else { return }
|
|
||||||
guard self.state == .writing else { return }
|
|
||||||
|
|
||||||
let current = self.dxSmartWriteIndex + 1
|
|
||||||
let total = self.dxSmartCommandQueue.count
|
|
||||||
let isNonFatal = self.dxSmartWriteIndex < 6
|
|
||||||
let isSaveConfig = self.dxSmartWriteIndex >= self.dxSmartCommandQueue.count - 1
|
|
||||||
|
|
||||||
if isSaveConfig {
|
|
||||||
// SaveConfig may not get a callback — beacon reboots. Treat as success.
|
|
||||||
DebugLog.shared.log("BLE: SaveConfig write timeout (beacon likely rebooted) — treating as success")
|
|
||||||
self.succeed()
|
|
||||||
} else if self.writeRetryCount < BeaconProvisioner.MAX_WRITE_RETRIES {
|
|
||||||
// Retry the write once
|
|
||||||
self.writeRetryCount += 1
|
|
||||||
DebugLog.shared.log("BLE: Write timeout for command \(current)/\(total) — retrying (\(self.writeRetryCount)/\(BeaconProvisioner.MAX_WRITE_RETRIES))")
|
|
||||||
if let commandChar = self.characteristics[BeaconProvisioner.DXSMART_COMMAND_CHAR] {
|
|
||||||
let packet = self.dxSmartCommandQueue[self.dxSmartWriteIndex]
|
|
||||||
self.scheduleWriteTimeout()
|
|
||||||
self.peripheral?.writeValue(packet, for: commandChar, type: .withResponse)
|
|
||||||
}
|
|
||||||
} else if isNonFatal {
|
|
||||||
// Non-fatal commands (first 6) — skip and continue
|
|
||||||
DebugLog.shared.log("BLE: Write timeout for non-fatal command \(current)/\(total) — skipping")
|
|
||||||
self.dxSmartWriteIndex += 1
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in
|
|
||||||
self?.dxSmartSendNextCommand()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Fatal command timed out after retry — fail
|
|
||||||
DebugLog.shared.log("BLE: Write timeout for critical command \(current)/\(total) — failing")
|
|
||||||
self.fail("Write timeout at step \(current)/\(total)", code: .writeFailed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeTimeoutTimer = timer
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + BeaconProvisioner.WRITE_TIMEOUT_SECONDS, execute: timer)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cancel any pending write timeout
|
|
||||||
private func cancelWriteTimeout() {
|
|
||||||
writeTimeoutTimer?.cancel()
|
|
||||||
writeTimeoutTimer = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - DX-Smart Packet Builder
|
// MARK: - DX-Smart Packet Builder
|
||||||
|
|
||||||
/// Build a DX-Smart CP28 packet: [4E][4F][CMD][LEN][DATA...][XOR_CHECKSUM]
|
/// Build a DX-Smart CP28 packet: [4E][4F][CMD][LEN][DATA...][XOR_CHECKSUM]
|
||||||
|
|
@ -1004,15 +940,6 @@ extension BeaconProvisioner: CBCentralManagerDelegate {
|
||||||
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
|
||||||
DebugLog.shared.log("BLE: Connected to \(peripheral.name ?? "unknown")")
|
DebugLog.shared.log("BLE: Connected to \(peripheral.name ?? "unknown")")
|
||||||
peripheral.delegate = self
|
peripheral.delegate = self
|
||||||
|
|
||||||
// Log negotiated MTU — CoreBluetooth auto-negotiates, but we need to verify
|
|
||||||
// the max write length can handle our largest packet (UUID write = ~21 bytes)
|
|
||||||
let maxWriteLen = peripheral.maximumWriteValueLength(for: .withResponse)
|
|
||||||
DebugLog.shared.log("BLE: Max write length (withResponse): \(maxWriteLen) bytes")
|
|
||||||
if maxWriteLen < 21 {
|
|
||||||
DebugLog.shared.log("BLE: WARNING — max write length \(maxWriteLen) may be too small for UUID packet (21 bytes)")
|
|
||||||
}
|
|
||||||
|
|
||||||
state = .discoveringServices
|
state = .discoveringServices
|
||||||
progress = "Discovering services..."
|
progress = "Discovering services..."
|
||||||
|
|
||||||
|
|
@ -1242,8 +1169,6 @@ extension BeaconProvisioner: CBPeripheralDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
|
||||||
cancelWriteTimeout() // Write callback received — cancel the per-write timeout
|
|
||||||
|
|
||||||
if let error = error {
|
if let error = error {
|
||||||
DebugLog.shared.log("BLE: Write failed for \(characteristic.uuid): \(error.localizedDescription)")
|
DebugLog.shared.log("BLE: Write failed for \(characteristic.uuid): \(error.localizedDescription)")
|
||||||
|
|
||||||
|
|
@ -1281,7 +1206,7 @@ extension BeaconProvisioner: CBPeripheralDelegate {
|
||||||
} else if isNonFatalCommand {
|
} else if isNonFatalCommand {
|
||||||
DebugLog.shared.log("BLE: Non-fatal command failed at step \(dxSmartWriteIndex + 1), continuing...")
|
DebugLog.shared.log("BLE: Non-fatal command failed at step \(dxSmartWriteIndex + 1), continuing...")
|
||||||
dxSmartWriteIndex += 1
|
dxSmartWriteIndex += 1
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
||||||
self?.dxSmartSendNextCommand()
|
self?.dxSmartSendNextCommand()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1326,7 +1251,7 @@ extension BeaconProvisioner: CBPeripheralDelegate {
|
||||||
DebugLog.shared.log("BLE: Device info query sent, waiting for response...")
|
DebugLog.shared.log("BLE: Device info query sent, waiting for response...")
|
||||||
} else {
|
} else {
|
||||||
dxSmartWriteIndex += 1
|
dxSmartWriteIndex += 1
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
|
||||||
self?.dxSmartSendNextCommand()
|
self?.dxSmartSendNextCommand()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue