Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

onCharacteristicWrite and onNotificationSent are being called too fast - how to acquire real outgoing data rates?

I have implemented duplex BLE communication (essentially, something like serial TX/RX cable) using two ATT characteristics on the peripheral - one is writable, the other is readable with notifications. Everything works as expected, except reported write speed.

When I call the following code:

txCharacteristic.setValue(data);
boolean queuedOk = 
   connectedPeripheralGatt.writeCharacteristic(txCharacteristic);

the onCharacteristicWrite callback is being triggered almost immediately, and then I repeat the code fragment above to apply next data fragment on the characteristic. While the receiving side is reporting correct speed (about 10 KB/s), as measured from timestamps collected in onCharacteristicWriteRequest, the sending side reports unbelievable speeds (like 50 - 300 KB/s) and it reports that it has finished pushing all the data to writeCharacteristic, although the receiving side still is receiving the incoming data.

So, clearly onCharacteristicWriteRequest is being called while most of the data is still on its way to the target device.

The same thing happens when I send the data from the peripheral to the central device, only this time onNotificationSent on the peripheral is lying about its speed and onCharacteristicChanged on the central is reporting correct speed.

Is there any more or less reliable way to approximately measure outgoing data rate from the sender's side without turning ACKs on (which I don't want to do for performance reasons)?

The data itself arrives intact, I'm testing it with image files of 0.5 - 5 megabytes of size and the images are always decoded correctly on the receiving side.

To maximize the throughput, the writable characteristic was set up to be non-reliable as follows:

    BluetoothGattCharacteristic rxCharacteristic = new BluetoothGattCharacteristic(UUID.fromString(RX_CHARACTERISTIC_UUID),
            BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE,
            BluetoothGattCharacteristic.PERMISSION_WRITE);
    rxCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);

I'm not sure of my use of PROPERTY_WRITE_NO_RESPONSE and WRITE_TYPE_NO_RESPONSE but from some articles on some websites it vaguely seemed that it's a good practice to specify both; correct me if I'm wrong.

I understand that with such setup I cannot expect real acknowledgements of the data on the receiving side, but still I expected that onCharacteristicWrite and onNotificationSent themselves would provide at least realistic timing to detect when the data was actually sent out of the source device. Instead, it seems, there is a pretty large cache which consumes my data and reports it being sent almost immediately.

like image 663
JustAMartin Avatar asked May 02 '17 15:05

JustAMartin


1 Answers

You are correct.

If you use "write with response" then the onCharacteristicWrite callback will be called after the remote sends back a write response. If you use "write without response" however, Android has a flow control mechanism which buffers the data you write. It works like this:

The app writes a packet. The Bluetooth stack puts this packet in its internal buffer. If there is still more space in the buffer after enqueuing this packet, the onCharacteristicWrite callback is called, so you can immediately write another packet. When the buffer is full, it waits to call the onCharacteristicWrite callback until there is now 50% space (if I remember correctly) available in the buffer.

As long as there are packets in the internal buffer, it tries to write them to the Bluetooth controller, which also has a limited number of packets that can be buffered. The Bluetooth controller sends a "number of packets complete" event back to Android's Bluetooth stack over HCI which indicates that the Link Layer of the remote's device has acknowledged a packet. (This doesn't indicate that the remote app hasn't received it; only that the remote Bluetooth controller received it.) If there is no space available in the Bluetooth controller, it will wait until there is space.

This is much smarter than how it works on iOS. There if you send a lot of "write with response" packets, they will be discarded before they are even sent if the internal buffer is full. (This can be solved by sending "write with response" each 10th packet or so).

Unfortunately (for your case), Android's Bluetooth stack does not send a callback to the app when a packet has been acknowledged by the remote Link Layer, so you will have to base your speed on the onCharacteristicWrite callback. You could however send status notifications in the other direction each 10th packet or so, which I think will give you good results. If Android's Bluetooth stack would instead send the onCharacteristicWrite callback when the Link Layer acknowledgement is received, that would reduce the speed to only one packet per connection event.

If the link sometimes get disconnected due to supervision timeout, you should be aware of the bug report I posted some time ago: https://issuetracker.google.com/issues/37121017.

About your questions with properties/permissions, that could have been the same thing but the Bluetooth team decided to separate those. The permissions kind of relate to the ATT protocol, telling the Bluetooth stack what a client is allowed to do. The properties are just some properties that the other side can read in order to know what type of characteristic it is.

like image 116
Emil Avatar answered Nov 13 '22 06:11

Emil