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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## 3.0.0
* **Breaking:** Add `QueueType.auto` which auto-selects the best queueing strategy per platform: Android uses a per-device queue, all other platforms run commands in parallel. It is now the default for both `UniversalBle` and `UniversalBlePeripheral`, replacing the previous `QueueType.global` default.
* iOS/macOS: Handle write-without-response transmit buffer backpressure
* iOS/macOS: complete concurrent reads, descriptor operations, notification changes, and RSSI reads one callback at a time.

## 2.3.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,24 @@ class CharacteristicWriteFuture: DeviceFuture {
}
}

class PendingWriteWithoutResponse: DeviceFuture {
let deviceId: String
let characteristic: CBCharacteristic
let data: Data
let result: (Result<Void, Error>) -> Void

init(deviceId: String, characteristic: CBCharacteristic, data: Data, result: @escaping (Result<Void, Error>) -> Void) {
self.deviceId = deviceId
self.characteristic = characteristic
self.data = data
self.result = result
}

func fail(with error: Error) {
result(.failure(error))
}
}

class CharacteristicNotifyFuture: DeviceFuture {
let deviceId: String
let characteristicId: String
Expand Down
50 changes: 29 additions & 21 deletions darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
private var activeServiceDiscoveries: [String: UniversalBleAsyncServiceDiscovery] = [:]
private var characteristicReadFutures = [CharacteristicReadFuture]()
private var characteristicWriteFutures = [CharacteristicWriteFuture]()
private var characteristicWriteWithoutResponseFutures = [CharacteristicWriteFuture]()
private var pendingWriteWithoutResponse = [PendingWriteWithoutResponse]()
private var characteristicNotifyFutures = [CharacteristicNotifyFuture]()
private var descriptorReadFutures = [DescriptorReadFuture]()
private var descriptorWriteFutures = [DescriptorWriteFuture]()
Expand Down Expand Up @@ -282,7 +282,7 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral

characteristicReadFutures.failAndRemoveAll(matching: deviceId, with: error)
characteristicWriteFutures.failAndRemoveAll(matching: deviceId, with: error)
characteristicWriteWithoutResponseFutures.failAndRemoveAll(matching: deviceId, with: error)
pendingWriteWithoutResponse.failAndRemoveAll(matching: deviceId, with: error)
characteristicNotifyFutures.failAndRemoveAll(matching: deviceId, with: error)
descriptorReadFutures.failAndRemoveAll(matching: deviceId, with: error)
descriptorWriteFutures.failAndRemoveAll(matching: deviceId, with: error)
Expand Down Expand Up @@ -406,27 +406,33 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
return
}

let type = bleOutputProperty == .withoutResponse ? CBCharacteristicWriteType.withoutResponse : CBCharacteristicWriteType.withResponse

if type == CBCharacteristicWriteType.withResponse {
if !gattCharacteristic.properties.contains(.write) {
switch bleOutputProperty {
case .withResponse:
guard gattCharacteristic.properties.contains(.write) else {
completion(Result.failure(createFlutterError(code: .characteristicDoesNotSupportWrite, message: "Characteristic does not support write withResponse")))
return
}
} else if type == CBCharacteristicWriteType.withoutResponse {
if !gattCharacteristic.properties.contains(.writeWithoutResponse) {
characteristicWriteFutures.append(CharacteristicWriteFuture(deviceId: deviceId, characteristicId: gattCharacteristic.uuid.uuidStr, serviceId: gattCharacteristic.service?.uuid.uuidStr, result: completion))
peripheral.writeValue(value.data, for: gattCharacteristic, type: .withResponse)
case .withoutResponse:
guard gattCharacteristic.properties.contains(.writeWithoutResponse) else {
completion(Result.failure(createFlutterError(code: .characteristicDoesNotSupportWriteWithoutResponse, message: "Characteristic does not support write withoutResponse")))
return
}
}
peripheral.writeValue(value.data, for: gattCharacteristic, type: type)

// Wait for future response
let future = CharacteristicWriteFuture(deviceId: deviceId, characteristicId: gattCharacteristic.uuid.uuidStr, serviceId: gattCharacteristic.service?.uuid.uuidStr, result: completion)
if type == CBCharacteristicWriteType.withResponse {
characteristicWriteFutures.append(future)
} else {
characteristicWriteWithoutResponseFutures.append(future)
if peripheral.canSendWriteWithoutResponse {
peripheral.writeValue(value.data, for: gattCharacteristic, type: .withoutResponse)
completion(Result.success(()))
} else {
pendingWriteWithoutResponse.append(
PendingWriteWithoutResponse(
deviceId: deviceId,
characteristic: gattCharacteristic,
data: value.data,
result: completion
)
)
}
}
}

Expand Down Expand Up @@ -697,12 +703,14 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral
}

public func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
characteristicWriteWithoutResponseFutures.removeAll { future in
if future.deviceId == peripheral.uuid.uuidString {
future.result(Result.success({}()))
return true
let deviceId = peripheral.uuid.uuidString
while peripheral.state == .connected && peripheral.canSendWriteWithoutResponse {
guard let index = pendingWriteWithoutResponse.firstIndex(where: { $0.deviceId == deviceId }) else {
return
}
return false
let pending = pendingWriteWithoutResponse.remove(at: index)
peripheral.writeValue(pending.data, for: pending.characteristic, type: .withoutResponse)
pending.result(Result.success(()))
}
}

Expand Down
Loading