yacine
yacine

Reputation: 13

rxandroidble write only sends the first 20B

I try to write >20 bytes data on a given (custom) characteristic. In the following log, I tried to write 85 bytes:

code:

connectionObservable
                .flatMap(rxBleConnection -> rxBleConnection.writeCharacteristic(
                        wChar.uuid(),
                        wChar.bytes()))
                .observeOn(mainThread())
                .subscribe(
                        bytes -> wChar.success(),
                        this::onWriteFailure
                );

result: On the server side (nrf52) I can see the EXEC_WRITE but only the first 20B are sent.

this is the logcat:

D/RxBle#ClientOperationQueue: QUEUED ConnectOperation(17461182) D/RxBle#ClientOperationQueue: STARTED ConnectOperation(17461182) D/RxBle#ClientOperationQueue: QUEUED ConnectOperation(218660306) D/RxBle#ClientOperationQueue: STARTED ConnectOperation(218660306) D/RxBle#BluetoothGatt: onConnectionStateChange newState=2 status=0 D/RxBle#BluetoothGatt: onConnectionStateChange newState=2 status=0 D/RxBle#ClientOperationQueue: FINISHED ConnectOperation(218660306) D/RxBle#ClientOperationQueue: FINISHED ConnectOperation(17461182) D/RxBle#ConnectionOperationQueue: QUEUED ServiceDiscoveryOperation(125599796) D/RxBle#ConnectionOperationQueue: STARTED ServiceDiscoveryOperation(125599796) D/RxBle#BluetoothGatt: onServicesDiscovered status=0 D/RxBle#ConnectionOperationQueue: QUEUED CharacteristicReadOperation(2626026) D/RxBle#ConnectionOperationQueue: FINISHED ServiceDiscoveryOperation(125599796) D/RxBle#ConnectionOperationQueue: STARTED CharacteristicReadOperation(2626026) D/RxBle#BluetoothGatt: onCharacteristicRead characteristic=0000fa03-0278-03be-4447-091eba91df8e status=0 D/RxBle#ConnectionOperationQueue: FINISHED CharacteristicReadOperation(2626026) D/RxBle#ClientOperationQueue: QUEUED ConnectOperation(158692575) D/RxBle#ClientOperationQueue: STARTED ConnectOperation(158692575) D/RxBle#BluetoothGatt: onConnectionStateChange newState=2 status=0 D/RxBle#ClientOperationQueue: FINISHED ConnectOperation(158692575) D/RxBle#ConnectionOperationQueue: QUEUED ServiceDiscoveryOperation(20778996) D/RxBle#ConnectionOperationQueue: STARTED ServiceDiscoveryOperation(20778996) > D/RxBle#BluetoothGatt:onServicesDiscovered status=0
D/RxBle#ConnectionOperationQueue: QUEUED CharacteristicWriteOperation(51009974) D/RxBle#ConnectionOperationQueue: FINISHED ServiceDiscoveryOperation(20778996) D/RxBle#ConnectionOperationQueue: STARTED CharacteristicWriteOperation(51009974)
> D/RxBle#BluetoothGatt: onCharacteristicWrite characteristic=0000fa04-0278-03be-4447-091eba91df8e status=0

D/RxBle#ConnectionOperationQueue: FINISHED CharacteristicWriteOperation(51009974)

I also tried to use the long rxAndroidBlewrite procedure:

connectionObservable
                .flatMap(rxBleConnection -> {
                            rxBleConnection.setupNotification(wChar.uuid()); 
                            return rxBleConnection.createNewLongWriteBuilder()
                                    .setCharacteristicUuid(wChar.uuid()) 
                                    .setBytes(array)
                                    .build();
                        }
                )
                .subscribe(
                        bytes -> wChar.success(),
                        this::onWriteFailure
                );

and it sends several successive write commands but it is not the long write procedure (with n ATT_prepare and 1 ATT_exec), it's independant writes:

D/RxBle#ConnectionOperationQueue: QUEUED CharacteristicLongWriteOperation(74131396) D/RxBle#ConnectionOperationQueue: FINISHED ServiceDiscoveryOperation(250008320) D/RxBle#ConnectionOperationQueue: STARTED CharacteristicLongWriteOperation(74131396)

D/RxBle#BluetoothGatt: onCharacteristicWrite characteristic=0000fa04-0278-03be-4447-091eba91df8e status=0

D/RxBle#BluetoothGatt: onCharacteristicWrite characteristic=0000fa04-0278-03be-4447-091eba91df8e status=0

D/RxBle#BluetoothGatt: onCharacteristicWrite characteristic=0000fa04-0278-03be-4447-091eba91df8e status=0

D/RxBle#BluetoothGatt: onCharacteristicWrite characteristic=0000fa04-0278-03be-4447-091eba91df8e status=0

D/RxBle#BluetoothGatt: onCharacteristicWrite characteristic=0000fa04-0278-03be-4447-091eba91df8e status=0

D/RxBle#ConnectionOperationQueue: FINISHED CharacteristicLongWriteOperation(74131396)

of course I could manage to rebuild at the server or to modify the MTU, but I want to use the BLE queued writes, which is normally supported by my central (rxandroidble) and my peripheral (nrf52)

Upvotes: 0

Views: 1089

Answers (3)

Dariusz Seweryn
Dariusz Seweryn

Reputation: 3222

If you refer to the queued writes then on the Android API it seems to be referenced as reliable write. This API is not currently implemented in the RxAndroidBle and you would need to do that by implementing RxBleCustomOperation API using a shortcut to native BluetoothGattCallback. Even then it appears that the native Android API is not fully functional in this matter.

The RxAndroidBle long write is not using the prepared writes but multiple standard writes. This actually could be better described in the Javadoc...

There are mixed opinions on what a Long Write really is. @Emil's excellent answer in this question clarifies it very well.

I have performed some tests using an nRF51822 with Softdevice S110 from SDK 8.1.0.

It seems that under the hood a Long Write is just a Prepared Write- Android manages it for the user.

On the peripheral side it seems to be trickier to implement as the Softdevice informs the app that the Prepared Write has finished and that data is ready to be parsed (it is not attached to the write BLE event itself). Parsing of he data belongs to the app logic as there seems to be no distinction between a Long Write and a Prepared/Reliable Write which may take into consideration writing to more than one characteristic at a time and that there may be some business logic related consistency issues (whether a specific set of writes should be accepted or not).

Conclusion: Android vanilla API (and RxAndroidBle) does support so called Long Write out of the box by making multiple Prepared/Queued writes under the hood. It is up to the peripheral's firmware to handle it properly

Upvotes: 1

Emil
Emil

Reputation: 18442

For the client side (Android), just use the standard Write procedure and you will be fine there. Internally, it will split it up into multiple Prepare Write Requests followed by an Execute Write Request. That is, use your first approach.

To help with the confusion, there are two layers: GATT and ATT. The ATT layer defines a concept called "Queued Writes" which consists of Prepare Write Request/Response and Execute Write Request/Response. The idea is that all Prepared Writes (each of these are limited to MTU-5 in size, have an offset parameter and an ATT handle) are put in a queue at the peripheral side. The writes are not committed at the peripheral until the Execute Write occurs. (The Execute Write has a flag which can also be used to cancel the whole queue.)

On the GATT layer, we have something called "Write Long Characteristic Values". This is a procedure which will be used when a Characteristic value should be written that exceeds what can be put in a single Write Request. This procedure is defined to use the "Queued Writes" features in ATT, so it will split the value into multiple segments, send them all in Prepared Write packets and finally the Execute Write packet will be sent. "Write Long Characteristic Values" should be avoided at all costs due to its large overhead (one roundtrip per packet, the response packets are long because they contain a copy of the value, and one final Execute Write Request is needed). Instead increasing the MTU to the max is much better since that can send everything in a single connection event if you are lucky.

Reliable Writes is also a GATT layer feature that uses the "Queued Writes" feature in ATT. The idea is that the user should be able to perform multiple atomic writes to potentially more than one characteristic in one operation. The word reliable comes from that the client is supposed to be verifying that the values were sent correctly. Each Prepared Write Response include the received value from the Prepared Write Request and the client should compare these and see that they are equal, and if not, abort. Now in Android, it's impossible with the current API+implementation to perform this check, so while the operation still works, it is really not as "reliable" as it is supposed to be (but you have CRC on all BLE packets anyway so I don't think there would be an issue). Note that if you follow the GATT rules you may only perform Reliable Writes to Characteristics (not Descriptors) and only the Characteristics declaring this property.

On the peripheral side, it's impossible to really know if the incoming Prepared Write Requests are part of a "Write Long Characteristic Values" operation or a "Reliable Write" operation. But anyway, most BLE stacks don't combine the retrieved portions and then delivers a single Write when the Execute Write is received. They instead expose a quite low level API in my opinion; often just more or less forward the ATT packets.

For Nordic Semiconductor's softdevice, the easiest method they have is to use "GATTS Queued Writes: Stack handled, no attributes require authorization" http://infocenter.nordicsemi.com/topic/com.nordic.infocenter.s132.api.v5.0.0/group___b_l_e___g_a_t_t_s___q_u_e_u_e_d___w_r_i_t_e___b_u_f___n_o_a_u_t_h___m_s_c.html. This way it queues up all Prepared Writes in your application-provided buffer and notifies the app when the Execute Write arrives. The app should then parse all (more or less) raw ATT Prepared Write Request packets the stack has put in the buffer. This structure is defined here http://infocenter.nordicsemi.com/index.jsp?topic=%2Fcom.nordic.infocenter.s132.api.v5.0.0%2Fgroup___b_l_e___g_a_t_t_s___q_u_e_u_e_d___w_r_i_t_e_s___u_s_e_r___m_e_m.html&cp=2_3_1_1_0_2_4_5. NOTE that the buffer is a list (an array with these structures concatenated) and not a single value. The list is terminated with an item containing BLE_GATT_HANDLE_INVALID as handle. I think your mistake is that you only parse the first item in this list.

Upvotes: 1

vdelricco
vdelricco

Reputation: 759

The Bluetooth 4.0 spec, which included the introduction of BLE, states that a maximum of 20 bytes can be transferred on a given characteristic at a time. If you need to send more data, you will have to send 20 bytes at a time in some type of loop.

So in fact, this isn't an issue with RxAndroidBle, just a limitation of the technology.

See here: https://stackoverflow.com/a/38914831/4321774

Upvotes: 1

Related Questions