Comments (5)
No detailed tests were performed.
Usually BLE is not about sending a bulk of data quickly but adding additional layers of abstraction and using RxJava obviously adds some computation work for the processor and memory allocation.
If I would have enough time I would perform some comparison test between specialised implementation of Android BLE API vs RxAndroidBle in firmware update scenario.
from rxandroidble.
We are currently using RxAndroidBle to flash the firmware of a device over the air. The firmware is about 300kb. Nice thing is that I also have a proof of concept app for the same flashing process using pure Android API.
These are not real experiments but simple rough estimate. Using RxAndroidBle, it takes up to 3 minutes to transfer everything as for native API, it's about 40s.
The big bottleneck in the library right now for heavy data transfer is that all write operations (in fact simply all operations) are done on the UI thread. This make the transfer process really longer than expected as it's fighting for the UI thread resources.
We noted a minimal 2x increase when just shutting down the screen of the device for example.
I read in the code that you're doing this because Samsung 4.3 fails when connectGatt
operation is not performed on the UI thread. Do you think it would be possible to make a conditional to only run the operations on the UI thread for a subset of all devices?
from rxandroidble.
from rxandroidble.
As a result of this issue, there will be a report of write operation benchmark (16kB) with nRF based mock device:
- Write with native Android APIs (write with BluetoothGatt and direct use of BluetoothGattCallback)
- Write with LongWriteOperationBuilder
- Looped writeCharacteristic operation of this library
- Custom operation with write with BluetoothGatt and response with RxBleGattCallback.
- Custom operation with write with BluetoothGatt and direct use of BluetoothGattCallback.
from rxandroidble.
Now I finally made some performance tests for sending out as much data as possible using various approaches.
Specification
Peripheral: nRF51822
with SoftDevice S110 8.0.0
Connection Interval: 11.25 ms
MTU: 23
(default—nRF51 does not support different MTUs)
Library version: 1.4.1
Test Algorithm
- The central (phone) is sending 19 packets with 20 bytes each to the peripheral. The first byte contains the index of the packet.
- After every 19 packets the peripheral sends a notification to the central with indexes of the packets it received. The first byte contains the index of the response. (i.e. the first response packet would contain [0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18], second [1, 19, 20, 21, ...])
- The central receives the notification and repeats from the first step.
Speed calculation
Every response notification (2. step) is timestamped. All responses are buffered for 60 seconds. We take the last notification timestamp and the first notification timestamp and calculate the time difference
. The resulting speed is ((number of received notifications - 1) * number of packets for each notification (19) * number of bytes in each packet (20)) / time difference
Code:
Observable.concat(
perform(device, this::unoptimizedSendUuid).map(aFloat -> "Unoptimized UUID: " + String.valueOf(aFloat) + " Bps"),
Observable.<String>empty().delay(1, TimeUnit.SECONDS),
perform(device, this::unoptimizedSendCharacteristic).map(aFloat -> "Unoptimized Char: " + String.valueOf(aFloat) + " Bps"),
Observable.<String>empty().delay(1, TimeUnit.SECONDS),
perform(device, this::longWriteSend).map(aFloat -> "Long Write: " + String.valueOf(aFloat) + " Bps"),
Observable.<String>empty().delay(1, TimeUnit.SECONDS),
perform(device, this::optimizedSend).map(aFloat -> "Optimized: " + String.valueOf(aFloat) + " Bps")
)
.observeOn(AndroidSchedulers.mainThread())
.doOnUnsubscribe(this::clearSubscription)
.subscribe(
result -> Log.e("RESULT", result),
e -> Log.e("ERROR", "Whoops!", e)
);
private Observable<Float> perform(RxBleDevice device, TestSetup testSetup) {
return device.establishConnection(false)
.flatMap(RxBleConnection::discoverServices,
(connection, services) -> services.getCharacteristic(genericCommunicationCharacteristicUuid)
.flatMap(
connection::setupNotification,
(characteristic, responseObs) -> {
characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
final Completable readyForTransmission =
responseObs.filter(bytes -> bytes[0] == 0x01).first().toCompletable();
final Observable<byte[]> speedTest = testSetup
.create(new TestConnection(connection, responseObs, characteristic, 19));
return readyForTransmission.andThen(speedTest);
}
)
.flatMap(observable -> observable)
)
.flatMap(observable -> observable)
.timestamp()
.buffer(1, TimeUnit.MINUTES)
.take(1)
.map(timestampeds -> {
final int bytesSent = (timestampeds.size() - 1) * 20 * 19;
final float timeSeconds = (timestampeds.get(timestampeds.size() - 1).getTimestampMillis() - timestampeds.get(0).getTimestampMillis()) * 0.001f;
return bytesSent / timeSeconds;
});
}
interface TestSetup {
Observable<byte[]> create(TestConnection testConnection);
}
static class TestConnection {
final RxBleConnection connection;
final Observable<byte[]> responseObs;
final BluetoothGattCharacteristic characteristic;
final int batchCount;
public TestConnection(RxBleConnection connection, Observable<byte[]> responseObs,
BluetoothGattCharacteristic characteristic, int batchCount) {
this.connection = connection;
this.responseObs = responseObs;
this.characteristic = characteristic;
this.batchCount = batchCount;
}
}
I have compared three approaches to sending data (all of them with setting BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE
):
- Using `RxBleConnection.writeCharacteristic(UUID, byte[])
private Observable<byte[]> unoptimizedSendUuid(TestConnection testConnection) {
final RxBleConnection connection = testConnection.connection;
final BluetoothGattCharacteristic characteristic = testConnection.characteristic;
final UUID uuid = characteristic.getUuid();
return Observable.range(0, testConnection.batchCount)
.concatMap(frameIndex -> connection.writeCharacteristic(
uuid,
new byte[]{
frameIndex.byteValue(), 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}
))
.ignoreElements()
.repeatWhen(observable -> Observable.zip(observable, testConnection.responseObs, (o, bytes) -> o))
.mergeWith(testConnection.responseObs);
}
- Using `RxBleConnection.writeCharacteristic(BluetoothGattCharacteristic, byte[])
private Observable<byte[]> unoptimizedSendCharacteristic(TestConnection testConnection) {
final RxBleConnection connection = testConnection.connection;
final BluetoothGattCharacteristic characteristic = testConnection.characteristic;
return Observable.range(0, testConnection.batchCount)
.concatMap(frameIndex -> connection.writeCharacteristic(
characteristic,
new byte[]{
frameIndex.byteValue(), 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}
))
.ignoreElements()
.repeatWhen(observable -> Observable.zip(observable, testConnection.responseObs, (o, bytes) -> o))
.mergeWith(testConnection.responseObs);
}
- Using
RxBleConnection.createNewLongWriteBuilder()
private Observable<byte[]> longWriteSend(TestConnection testConnection) {
final byte[] bytesToSend = new byte[20 * 19];
for (int i = 0; i < bytesToSend.length; i = i + 20) {
bytesToSend[i] = (byte) (i / 20);
}
return testConnection.connection
.createNewLongWriteBuilder()
.setBytes(bytesToSend)
.setCharacteristic(testConnection.characteristic)
.build()
.ignoreElements()
.repeatWhen(observable -> Observable.zip(observable, testConnection.responseObs, (o, bytes) -> o))
.mergeWith(testConnection.responseObs);
}
- Using
RxBleConnection.queue(RxBleCustomOperation)
Please note that this implementation is not a example to follow—it does not check for the disconnection of the peripheral or cancellation of the operation.
private Observable<byte[]> optimizedSend(TestConnection testConnection) {
return testConnection.connection.queue((bluetoothGatt, rxBleGattCallback, scheduler) -> Observable.create(
emitter -> {
Log.i("START", String.valueOf(testConnection.batchCount));
final byte[] data = new byte[20];
testConnection.characteristic.setValue(data);
final AtomicBoolean writeCompleted = new AtomicBoolean(false);
final AtomicBoolean ackCompleted = new AtomicBoolean(false);
final AtomicInteger batchesSent = new AtomicInteger(0);
final Runnable writeNextBatch = () -> {
data[0]++;
if (!bluetoothGatt.writeCharacteristic(testConnection.characteristic)) {
emitter.onError(new BleGattCannotStartException(bluetoothGatt, BleGattOperationType.CHARACTERISTIC_WRITE));
} else {
Log.i("SEND", String.valueOf(data[0]));
batchesSent.incrementAndGet();
}
};
final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status != BluetoothGatt.GATT_SUCCESS) {
emitter.onError(new BleGattException(gatt, status, BleGattOperationType.CHARACTERISTIC_WRITE));
} else if (batchesSent.get() == testConnection.batchCount) {
if (ackCompleted.get()) {
batchesSent.set(0);
ackCompleted.set(false);
emitter.onNext(null);
writeNextBatch.run();
} else {
writeCompleted.set(true);
}
} else {
characteristic.setValue(data);
writeNextBatch.run();
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
final byte[] bytes = characteristic.getValue();
Log.i("ACK", Arrays.toString(bytes) + "/" + bytes.length + "/" + System.identityHashCode(bytes));
characteristic.setValue(data);
if (writeCompleted.get()) {
batchesSent.set(0);
writeCompleted.set(false);
emitter.onNext(null);
writeNextBatch.run();
} else {
ackCompleted.set(true);
}
}
};
rxBleGattCallback.setNativeCallback(bluetoothGattCallback);
Log.i("SEND", String.valueOf(data[0]));
if (!bluetoothGatt.writeCharacteristic(testConnection.characteristic)) {
emitter.onError(new BleGattCannotStartException(bluetoothGatt, BleGattOperationType.CHARACTERISTIC_WRITE));
} else {
batchesSent.incrementAndGet();
}
},
Emitter.BackpressureMode.NONE
));
}
Results (in Bytes per second)
central \ implementation | unoptimizedSendUuid |
unoptimizedSendCharacteristic |
longWriteSend |
optimizedSend |
---|---|---|---|---|
Micromax Canvas A107 (5.0) | 2463.4 | 2544.2 | 3479.5 | 4604.4 |
Nexus 5 (6.0.1) | 2555.1 | 2593.2 | 2657.8 | 2759.1 |
Samsung Galaxy S6 SM-G920F (7.0) | 1631.9 | 1789.4 | 2290.3 | 3867.7 |
Motorola Droid XT1030 (4.4.4) | 1737.2 | 1960.8 | 2707.6 | 3010.1 |
Asus Zenfone 5 T00J (4.4.2) | 1589.2 | 1758.0 | 2450.2 | 4010.2 |
Google Pixel (8.0.0) | 2513.5 | 2646.9 | 2835.7 | 4602.5 |
Results for Connection Interval = 100 ms
central \ implementation | unoptimizedSendUuid |
unoptimizedSendCharacteristic |
longWriteSend |
optimizedSend |
---|---|---|---|---|
Micromax Canvas A107 (5.0) | 576.0 | 571.9 | 567.8 | 570.8 |
Nexus 5 (6.0.1) | 605.0 | 612.0 | 623.9 | 624.5 |
Samsung Galaxy S6 SM-G920F (7.0) | 629.0 | 619.9 | 623.2 | 632.3 |
Motorola Droid XT1030 (4.4.4) | 342.3 | 342.9 | 343.0 | 344.8 |
Asus Zenfone 5 T00J (4.4.2) | 546.7 | 542.9 | 560.7 | 545.7 |
Samsung Galaxy S3 GT-I9300 (4.3) | 579.2 | 585.6 | 577.3 | 568.7 |
Google Pixel (8.0.0) | 757.3 | 757.3 | 758.6 | 758.7 |
Conclusion
- When interacting with a low
Connection Interval
peripheral it may be worth to use native implementation or custom operation to mitigate the downturn thatRxJava
causes. - When the
Connection Interval
grows the benefit from optimisations are less visible. It could need a detailed research but it seems that forConnection Interval > 50 ms
every implementation should perform similarly. - The potential throughput of different Android handsets varies a lot (even in the same environment) because of the raw speed of the device (the route time of going through all the system layers from
BluetoothGatt.writeCharacteristic()
toBluetoothGattCallback.onCharacteristicWrite()
) and number of buffers that it's Bluetooth Chip has.
I hope this quick research helps you.
Best Regards
from rxandroidble.
Related Issues (20)
- It is not possible to disconnect the Bluetooth while it is in the process of connecting. HOT 1
- BleGattCharacteristicException while readCharacteristic HOT 12
- scanResult.bleDevice.name not showing Ble updated name HOT 1
- BLUETOOTH_SCAN set usesPermissionFlags="neverForLocation",Is there a problem? HOT 2
- GenericFailure<CharacteristicValueUpdateError> HOT 11
- Send long bytes twice HOT 2
- Write request is not processed in the random order HOT 2
- Long Bytes Send HOT 8
- Creating a Fork HOT 2
- Set Preferred PHY / Read PHY HOT 2
- RxBleClient mulit connections HOT 4
- Is the library still maintained? HOT 3
- Android 14 breaking change HOT 15
- Google Pixel 5 loses bonding info after BT adapter cycle HOT 4
- Metode readcharacteristic not stream HOT 2
- Unable to readCharacteristic after writeCharacteristic HOT 5
- In the case of unsuccessful Bluetooth connection, actively disconnect without executing context. unregisterReceiver (receiver); At this point, there may be multiple Bluetooth broadcast registrarReceivers, which may crash on some Huawei phones due to a broadcast limit of approximately 1000. Suggest fixing this issue. HOT 2
- Can't scan after connect dispose/clear HOT 1
- Add `advertisingSid` property to `ScanResult` and `RxBleInternalScanResult` HOT 3
- BleGattCallbackTimeoutException when writing a characteristic with different data payload many times in a short period of time. HOT 18
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from rxandroidble.