Compare commits

..

No commits in common. "b88dded928cbcaac83215c4363d99824a358573d" and "6eaccb6bf602f625d4129e486f550d1499037b9c" have entirely different histories.

3 changed files with 27 additions and 41 deletions

View file

@ -448,18 +448,10 @@ extension DXSmartProvisioner: CBPeripheralDelegate {
return
}
// For command writes (FFE1/FFE2): the .withResponse write confirmation
// IS the ACK. Some commands (e.g. 0x61 Frame1_DevInfo) don't send a
// separate FFE1 notification, so we must resolve here on success too.
// If a notification also arrives later, responseContinuation will already
// be nil harmless.
if let cont = responseContinuation {
// Handle write errors for command writes
if let error, let cont = responseContinuation {
responseContinuation = nil
if let error {
cont.resume(throwing: error)
} else {
cont.resume(returning: Data())
}
cont.resume(throwing: error)
}
}
}

View file

@ -65,7 +65,7 @@ final class BLEManager: NSObject, ObservableObject {
])
scanTimer = Timer.scheduledTimer(withTimeInterval: duration, repeats: false) { [weak self] _ in
DispatchQueue.main.async {
Task { @MainActor in
self?.stopScan()
}
}
@ -235,27 +235,26 @@ final class BLEManager: NSObject, ObservableObject {
extension BLEManager: CBCentralManagerDelegate {
nonisolated func centralManagerDidUpdateState(_ central: CBCentralManager) {
let state = central.state
DispatchQueue.main.async { [weak self] in
self?.bluetoothState = state
Task { @MainActor in
bluetoothState = central.state
}
}
nonisolated func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
DispatchQueue.main.async { [weak self] in
self?.onPeripheralConnected?(peripheral)
Task { @MainActor in
onPeripheralConnected?(peripheral)
}
}
nonisolated func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
DispatchQueue.main.async { [weak self] in
self?.onPeripheralFailedToConnect?(peripheral, error)
Task { @MainActor in
onPeripheralFailedToConnect?(peripheral, error)
}
}
nonisolated func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
DispatchQueue.main.async { [weak self] in
self?.onPeripheralDisconnected?(peripheral, error)
Task { @MainActor in
onPeripheralDisconnected?(peripheral, error)
}
}
@ -265,18 +264,13 @@ extension BLEManager: CBCentralManagerDelegate {
advertisementData: [String: Any],
rssi RSSI: NSNumber
) {
// Capture values in nonisolated context before hopping to main
let name = peripheral.name ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? ""
let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
let mfgData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
let isConnectable = advertisementData[CBAdvertisementDataIsConnectable] as? Bool ?? false
let peripheralId = peripheral.identifier
let rssiValue = RSSI.intValue
Task { @MainActor in
let name = peripheral.name ?? advertisementData[CBAdvertisementDataLocalNameKey] as? String ?? ""
let serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID]
let mfgData = advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data
let isConnectable = advertisementData[CBAdvertisementDataIsConnectable] as? Bool ?? false
DispatchQueue.main.async { [weak self] in
guard let self else { return }
let type = self.detectBeaconType(name: name, serviceUUIDs: serviceUUIDs, manufacturerData: mfgData)
let type = detectBeaconType(name: name, serviceUUIDs: serviceUUIDs, manufacturerData: mfgData)
// Match Android behavior (lines 164-169):
// Include devices that have a recognized type, OR
@ -284,31 +278,31 @@ extension BLEManager: CBCentralManagerDelegate {
// are connectable with a name (potential configurable beacon)
if type == .unknown {
let hasName = !name.isEmpty
let hasIBeaconData = mfgData.flatMap { self.parseIBeaconData($0) } != nil
let hasIBeaconData = mfgData.flatMap { parseIBeaconData($0) } != nil
if !hasIBeaconData && !(isConnectable && hasName) {
return
}
}
if let idx = self.discoveredBeacons.firstIndex(where: { $0.id == peripheralId }) {
if let idx = discoveredBeacons.firstIndex(where: { $0.id == peripheral.identifier }) {
// Update existing
self.discoveredBeacons[idx].rssi = rssiValue
self.discoveredBeacons[idx].lastSeen = Date()
discoveredBeacons[idx].rssi = RSSI.intValue
discoveredBeacons[idx].lastSeen = Date()
} else {
// New beacon
let beacon = DiscoveredBeacon(
id: peripheralId,
id: peripheral.identifier,
peripheral: peripheral,
name: name,
type: type,
rssi: rssiValue,
rssi: RSSI.intValue,
lastSeen: Date()
)
self.discoveredBeacons.append(beacon)
discoveredBeacons.append(beacon)
}
// Keep list sorted by RSSI (strongest/closest first)
self.discoveredBeacons.sort { $0.rssi > $1.rssi }
discoveredBeacons.sort { $0.rssi > $1.rssi }
}
}
}

View file

@ -631,7 +631,7 @@ struct ScanView: View {
// Monitor for unexpected disconnects during provisioning
bleManager.onPeripheralDisconnected = { [weak provisionLog] peripheral, error in
if peripheral.identifier == beacon.peripheral.identifier {
DispatchQueue.main.async { [weak self] in
Task { @MainActor [weak self] in
let reason = error?.localizedDescription ?? "beacon timed out"
provisionLog?.log("disconnect", "Unexpected disconnect: \(reason)", isError: true)
guard let self = self else { return }