Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
## 3.0.0
* iOS/macOS: complete concurrent reads, descriptor operations, notification changes, and RSSI reads one callback at a time.

## 2.3.0
* Windows: support connectionless manufacturer-data advertising without a GATT service, including state/error reporting and cleanup on stop/disposal.
* Apple: Accurately report manufacturer-data advertising capabilities.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,13 @@ protocol DeviceFuture {
func fail(with error: Error)
}

extension Array {
mutating func popFirst(where predicate: (Element) throws -> Bool) rethrows -> Element? {
guard let index = try firstIndex(where: predicate) else { return nil }
return remove(at: index)
}
}

extension Array where Element: DeviceFuture {
mutating func failAndRemoveAll(matching deviceId: String, with error: Error) {
removeAll { future in
Expand Down
157 changes: 78 additions & 79 deletions darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -725,66 +725,66 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
}

public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
descriptorReadFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString &&
future.descriptorId == descriptor.uuid.uuidStr &&
future.characteristicId == descriptor.characteristic?.uuid.uuidStr &&
future.serviceId == descriptor.characteristic?.service?.uuid.uuidStr {
if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("READ_DESCRIPTOR_FAILED <- \(peripheral.uuid.uuidString) \(descriptor.uuid.uuidStr): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else {
if let valueData = descriptor.value as? Data {
future.result(Result.success(FlutterStandardTypedData(bytes: valueData)))
} else if let numberVal = descriptor.value as? NSNumber {
var val = numberVal.uint16Value
let data = Data(bytes: &val, count: MemoryLayout<UInt16>.size)
future.result(Result.success(FlutterStandardTypedData(bytes: data)))
} else if let stringVal = descriptor.value as? String {
let data = Data(stringVal.utf8)
future.result(Result.success(FlutterStandardTypedData(bytes: data)))
} else if let cbuuid = descriptor.value as? CBUUID {
future.result(Result.success(FlutterStandardTypedData(bytes: cbuuid.data)))
} else {
future.result(Result.success(FlutterStandardTypedData(bytes: Data())))
}
}
return true
}
return false
guard let future = descriptorReadFutures.popFirst(where: {
$0.deviceId == peripheral.uuid.uuidString &&
$0.descriptorId == descriptor.uuid.uuidStr &&
$0.characteristicId == descriptor.characteristic?.uuid.uuidStr &&
$0.serviceId == descriptor.characteristic?.service?.uuid.uuidStr
}) else {
return
}

if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("READ_DESCRIPTOR_FAILED <- \(peripheral.uuid.uuidString) \(descriptor.uuid.uuidStr): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else if let valueData = descriptor.value as? Data {
future.result(Result.success(FlutterStandardTypedData(bytes: valueData)))
} else if let numberVal = descriptor.value as? NSNumber {
var val = numberVal.uint16Value
let data = Data(bytes: &val, count: MemoryLayout<UInt16>.size)
future.result(Result.success(FlutterStandardTypedData(bytes: data)))
} else if let stringVal = descriptor.value as? String {
let data = Data(stringVal.utf8)
future.result(Result.success(FlutterStandardTypedData(bytes: data)))
} else if let cbuuid = descriptor.value as? CBUUID {
future.result(Result.success(FlutterStandardTypedData(bytes: cbuuid.data)))
} else {
future.result(Result.success(FlutterStandardTypedData(bytes: Data())))
}
}

public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
descriptorWriteFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString &&
future.descriptorId == descriptor.uuid.uuidStr &&
future.characteristicId == descriptor.characteristic?.uuid.uuidStr &&
future.serviceId == descriptor.characteristic?.service?.uuid.uuidStr {
if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("WRITE_DESCRIPTOR_FAILED <- \(peripheral.uuid.uuidString) \(descriptor.uuid.uuidStr): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else {
future.result(Result.success({}()))
}
return true
}
return false
guard let future = descriptorWriteFutures.popFirst(where: {
$0.deviceId == peripheral.uuid.uuidString &&
$0.descriptorId == descriptor.uuid.uuidStr &&
$0.characteristicId == descriptor.characteristic?.uuid.uuidStr &&
$0.serviceId == descriptor.characteristic?.service?.uuid.uuidStr
}) else {
return
}

if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("WRITE_DESCRIPTOR_FAILED <- \(peripheral.uuid.uuidString) \(descriptor.uuid.uuidStr): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else {
future.result(Result.success({}()))
}
}

public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
characteristicNotifyFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr {
if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("SET_NOTIFY_FAILED <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else {
future.result(Result.success({}()))
}
return true
}
return false
guard let future = characteristicNotifyFutures.popFirst(where: {
$0.deviceId == peripheral.uuid.uuidString &&
$0.characteristicId == characteristic.uuid.uuidStr &&
$0.serviceId == characteristic.service?.uuid.uuidStr
}) else {
return
}

if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("SET_NOTIFY_FAILED <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else {
future.result(Result.success({}()))
}
}

Expand Down Expand Up @@ -826,37 +826,36 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
return
}

// Update futures for readValue
characteristicReadFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr {
if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("READ_FAILED <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else {
if let characteristicValue = characteristic.value {
future.result(Result.success(FlutterStandardTypedData(bytes: characteristicValue)))
} else {
future.result(Result.failure(createFlutterError(code: .readFailed, message: "No value")))
}
}
return true
}
return false
guard let future = characteristicReadFutures.popFirst(where: {
$0.deviceId == peripheral.uuid.uuidString &&
$0.characteristicId == characteristic.uuid.uuidStr &&
$0.serviceId == characteristic.service?.uuid.uuidStr
}) else {
return
}

if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("READ_FAILED <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else if let characteristicValue = characteristic.value {
future.result(Result.success(FlutterStandardTypedData(bytes: characteristicValue)))
} else {
future.result(Result.failure(createFlutterError(code: .readFailed, message: "No value")))
}
}

public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
rssiReadFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString {
if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("READ_RSSI_FAILED <- \(peripheral.uuid.uuidString): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else {
future.result(Result.success(RSSI.int64Value))
}
return true
}
return false
guard let future = rssiReadFutures.popFirst(where: {
$0.deviceId == peripheral.uuid.uuidString
}) else {
return
}

if let flutterError = error?.toFlutterError() {
UniversalBleLogger.shared.logError("READ_RSSI_FAILED <- \(peripheral.uuid.uuidString): \(flutterError.message ?? "")")
future.result(Result.failure(flutterError))
} else {
future.result(Result.success(RSSI.int64Value))
}
}
}
Expand Down
Loading