Repository navigation
feat: add writeCharacteristicWithoutResponseBatch for bulk transfers - #944
Open
minhtri1401 wants to merge 3 commits into
Open
minhtri1401 wants to merge 3 commits into
minhtri1401 wants to merge 3 commits into
Conversation
CoreBluetooth silently drops write-without-response packets when they are written while `canSendWriteWithoutResponse` is false. Queue such writes per peripheral and flush them from `peripheralIsReady(toSendWriteWithoutResponse:)`, completing the Flutter call only once the packet is accepted. Pending writes fail when the peripheral disconnects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sends a whole chunk in one call. The native side splits it into MTU - 3 packets and sends them back to back without a Dart round trip per packet: RxAndroidBle's long-write builder on Android, the write-without-response flow-control queue on iOS/macOS. The characteristic is resolved once per batch. Refs PhilipsHue#940 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
minhtri1401
marked this pull request as draft
October 6, 2026 15:35
minhtri1401
marked this pull request as ready for review
October 6, 2026 19:01
Peripherals that treat one ATT write as one protocol frame need packets smaller than MTU - 3. packetSize (0/null = platform default) sets RxAndroidBle's maxBatchSize on Android and the slice size on iOS/macOS, clamped to maximumWriteValueLength. Doc comments now describe the gain as reduced per-write overhead rather than more packets per connection event. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
Firmware updates over BLE with
writeCharacteristicWithoutResponseare much slower than the same transfer in a native app. Each packet costs a full Dart ↔ native round trip before the next one can start:resolveSingle()→getDiscoveredServices()before every write. That is an extra method-channel call, and on Android it re-serializes the whole GATT table to protobuf for every packet.The link sits idle during that round trip, so the phone sends fewer packets per connection event than a native app that writes the next packet straight from the
onCharacteristicWritecallback.Related upstream issue: #940
Change
This PR has two commits.
1. fix(ios): respect write-without-response flow control
On iOS/macOS the plugin called
writeValue(_:for:type: .withoutResponse)without checkingcanSendWriteWithoutResponse. CoreBluetooth silently drops packets written while its transmit queue is full. On fast centrals (e.g. iPhone 17) this corrupts OTA images.peripheralIsReady(toSendWriteWithoutResponse:).Behaviour change: on iOS/macOS, the Future returned by the existing
writeCharacteristicWithoutResponsemay now take longer to complete while the transmit queue is full. Before, it completed immediately even if the packet was dropped. Android is unchanged.2. feat: add
writeCharacteristicWithoutResponseBatchfor bulk transfersNew API: one call sends a whole chunk. The native side splits it into MTU − 3 packets and sends them in order with no Dart round trip between packets.
RxBleConnection.writeCharWithoutResponseBatchuses RxAndroidBle'screateNewLongWriteBuilder(). The whole chunk is a single queued operation. Each packet is written as soon as the previousonCharacteristicWritearrives, with a timeout per packet.maximumWriteValueLength(for: .withoutResponse)and the packets go into the per-peripheral flow-control queue from commit 1. The Flutter call completes once, after the last packet is accepted or on the first error.ReactiveBlePlatform(throwsUnimplementedErrorby default, like the other methods),ReactiveBleMobilePlatform,ConnectedDeviceOperationandFlutterReactiveBle. The characteristic is resolved once per batch. An empty value returns immediately.packetSize(optional): size of each packet. Defaults to MTU − 3. Use it when the peripheral treats one ATT write as one protocol frame. Must be positive and no larger than MTU − 3; iOS/macOS clamp it tomaximumWriteValueLength, Android does not check it. Sent aspacket_size = 3inWriteCharacteristicRequest; 0 means default, so existing callers are unchanged.WriteCharacteristicRequest/WriteCharacteristicInfowith a new method-channel name,writeCharacteristicWithoutResponseBatch.Behaviour:
Test results
Same 207 KB application image (206,922–207,114 bytes across runs), custom STM32 peripheral, debug builds, one phone per platform. Speeds are KB = 1000 bytes.
writeCharacteristicWithoutResponseper packetWhat the Android columns show:
requestMtufirst; on Android 14+ the stack negotiated 251 regardless of the 247 requested.Not yet done: second Android MTU-251 run, iOS re-run with the MTU logged, flash verification on the device after the MTU-251 run.
Automated (Flutter 3.47.5):
reactive_ble_platform_interface,reactive_ble_mobileandflutter_reactive_bleDart tests pass, including a new test for the batch method-channel call, pluspacketSizetests (converter, platform,ArgumentError)flutter analyzereports no issues in all three packages./gradlew testDebugUnitTest) pass: 66 tests, including the newReactiveBleClientTestbatch test./gradlew detektpassesflutter build ios --debug --no-codesignsucceeds for the example app