Skip to content

Tutorial: MTU negotiation

Dariusz Seweryn edited this page Jun 16, 2020 · 6 revisions

Introduction

BLE transmission happen in packets. The default size of such a packet is called a Maximum Transfer Unit. The default is 23 bytes. 3 bytes are nominally reserved for lower level data so the user is able to send/read 20 bytes at a time in every such packet.

There is however a possibility to negotiate a higher MTU. It was introduced in Android 5.0 to the API though it was possible to start the negotiation from the peripheral side even before.

There is decent explanation how to optimally choose MTU value by Chris Coleman originally available here. Long story short – optimal MTU in terms of throughput [given default Packet Data Unit (PDU) / no data length extension] is:

n * 27 - 4

Where n is an integer value. So these are the values:

23, 50, 77, 104, 131, 158, 185, 212, 239, 266, 293, 320, 347, 374, 401, 428, 455, 482, 509...

Example

Below is an example how a MTU negotiating Observable.Transformer<RxBleConnection, RxBleConnection> could look like:

Java

private ObservableTransformer<RxBleConnection, RxBleConnection> mtuNegotiationObservableTransformer = upstream -> {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        return upstream.doOnSubscribe(ignoredDisposable -> Log.i("MTU", "MTU negotiation is not supported"));
    }

    return upstream
            .doOnSubscribe(ignoredDisposable -> Log.i("MTU", "MTU negotiation is supported"))
            .flatMapSingle(connection ->
                    connection.requestMtu(GATT_MTU_MAXIMUM)
                            .doOnSubscribe(ignoredDisposable -> Log.i("MTU", "Negotiating MTU started"))
                            .doOnSuccess(mtu -> Log.i("MTU", "Negotiated MTU: " + mtu))
                            .ignoreElement()
                            .andThen(Single.just(connection)));
};

Usage:

Disposable d = bleDevice.establishConnection(false)
        .compose(mtuNegotiationObservableTransformer)
        .subscribe(
                rxBleConnection -> {
                },
                throwable -> {
                }
        );

Kotlin

val MtuNegotiationObservableTransformer = object : ObservableTransformer<RxBleConnection, RxBleConnection> {
    override fun apply(upstream: Observable<RxBleConnection>): ObservableSource<RxBleConnection> {

        return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            upstream.doOnSubscribe { Log.i("MTU", "MTU negotiation is not supported") }
        } else {
            upstream.doOnSubscribe { Log.i("MTU", "MTU negotiation is supported") }
                    .flatMapSingle { connection ->
                        connection.requestMtu(GATT_MTU_MAXIMUM)
                                .doOnSubscribe { Log.i("MTU", "Negotiating MTU started") }
                                .doOnSuccess { Log.i("MTU", "Negotiated MTU: $it") }
                                .ignoreElement()
                                .andThen(Single.just(connection))
                    }
        }
    }
}

Usage:

val disposable = bleDevice.establishConnection(false)
        .compose(MtuNegotiationObservableTransformer)
        .subscribe(
                {  },
                {  }
        )