If I want to transfer a lot of data (e.g. 1 MB file) over BLE, what's the best way to do it?
I control both sides of the connection, but the client side is iOS/Android so only has access to GATT. I can't do anything with L2CAP.
I also can't wait for Bluetooth 4.1, 6LoWPAN, Connection-Oriented-Channels or anything like that.
I would assume the answer is to have one "request" characteristic that you write a data request to ("Give me 3000 bytes starting at byte 0"), and a "data out" characteristic that sends lots of 20 byte notifications (the maximum characteristic size) containing the data.
Is there a better way?
Yes we are using the approach you have mentioned.
Request data with the last index number(First time the index is 0)
The server send you with data with index no.Store the index no for subsequent format
continue Step 1 and 2 till the time server sends end of data-probably with index -1 or something.
Make sure you transfer the data you required in the most space efficient format.See if you can zip the files and transfer it.
You can update connection interval to small value with smallest 6*1.25 ms in remote BLE device.
Actually, BLE is designed for Low energy, small packet, low data rate.
L2cap data will be transmitted in different data channel with frequency hop. Packets TX/RX happen within each connection interval and max number of packets TX/RX in an event is restricted by specification, finally implemented by manufacture. So we can change connection interval as small as possible to increase data rate.
Refer BT 4.0 spec Vol 2, 7.8.18 LE Connection Update Command.
Try to negotiate a larger MTU than the default.
Then each notification can be larger. Even though it will be fragmented by the L2CAP layer, you will get a slightly larger throughput since the packet header will be smaller.
Related
I understand that in Android, the MTU for writing data to a Characterisitc is around 23 bytes (3 bytes used so you have around 20 bytes free) and that you can request a higher MTU (up to 512) to allow you to write/send larger data packets to a bluetooth device.
However, do you also need to do this when reading data from a characterisitic?
Both Reading and Notification updates?
If say, I have a characteristic that sends data in 123 byte chunks, but I only ever have to send at most 2 bytes to it, do I need to negotiate a large MTU?
I can recommend you to read the ATT and GATT chapters in the Bluetooth Core standard. Those explain the protocol.
By default Android does not negotiate a larger MTU than the default (23 bytes). You can do that yourself though by calling the requestMtu function.
Android automatically under the hood uses "Write Long Characteristic Values" and "Read Long Characteristic Values" when the MTU is not big enough when reading/writing values in order to transfer the whole value. However these procedures are very inefficient since they require multiple roundtrips. The read operation also is not atomic.
Notifications and Indications don't have any "Long" variant with multiple roundtrips, so these will be truncated to fit the MTU.
With a normal Characteristics Read only the MTU Size (20bytes) of data will be read.
My customer will offer a characteristics with a larger size (about 100bytes).
I saw that BLE offers a "Long Read" feature which reads until the size of the characteristics is reached.
(https://bluegiga.zendesk.com/entries/25053373--REFERENCE-BLE-master-slave-GATT-client-server-and-data-RX-TX-basics)
attclient_read_long command - Starts a procedure where the client first sends normal read request to the server, and if the server returns an attribute value with a length equal to the BLE MTU (22 bytes), then the client continues to send "read long" requests until rest of the attribute is read. This only applies if you are reading attributes that are longer than 22 bytes. It is often simpler to construct your GATT server such that there are no long attributes, for simplicity. Note that the BLE protocol still requires that data is packetized into max. 22-byte chunks, so using "read long" does not save transmission time.
But how can I use this feature in Android?
The BluetoothGatt class only offers a simple "Read()" - same for iOS.
Increasing the MTU is not possible since we need to support devices with AP Level < 21 (increaseMTU was introduced at API 21)
I can confirm for iOS that a read operation as per standard will occur first. Then if the server returns a completely filled PDU, the iOS device will then continue to perform the blob read operation. Tested with iPhone 7 running iOS 11.2.x
You do NOT need to call the peripheral.readValue(characteristic) multiple times for long attributes. CoreBluetooth does all of this under the covers.
Refer to the Bluetooth Spec Core v5.0, specifically Vol 3, Part F. "Long Attribute Values".
Experiment to prove above.
I have an Android Thing acting as the server that I'm making return the maximum length with my iPhone during a read operation. iOS and my RPI3 exchange a MTU of 185. So the read response is (MTU - 1) 184 bytes long. The server (RPI) then receives a new read request with an offset of 184, which you can then return more data. This is continued until the offset is > 512, or the last read response returns less than the MTU - 1 length.
Based upon the fact that the BluetoothGattServer supports long attributes, I'd assume the BluetoothGatt object does as well. Since there is no way via the API to set the offset to be read, I'd assume you can invoke read just once.
I have an android client that functions as a central and have an app on my MAC (peripheral) that this central connects to and sends data.
At this point, I need to wait almost 100ms after I call writeCharacteristic(..) to receive the onCharacteristicWrite(..) callback. I am sending strings. If I send smaller strings, the throughput is great (understandably). When the string contains about 200 characters and I send 20 byte chunks, it takes almost a second before the entire string is seen at the peripheral. When I set the write type to NO_RESPONSE before writing the characteristic, I see no data on the peripheral.
After I connect, I have done the following to improve throughput:
Stopped discovery after services are discovered because it is an expensive operation
I set the write type to default first - When I do this, I see data on the peripheral. But, there is a significant delay. When I set the writeType to NO_RESPONSE, I see no data on the peripheral. I have no logic in onCharacteristicWrite(..) either. Sometimes, I see the data getting truncated on the peripheral.
I have set the desired connection latency to low on my mac app. Is there a way to set a value (as 7.5ms perhaps?).
When I set the write type to default and send a string of 200 characters - I split the string into 20 byte chunks. I now have 10 chunks to send. If I set characteristic value and call writeCharacteristic(..) in loop, I see no data. When I add a ~100ms delay after writeCharacteristic(..) before it executes the next iteration of the loop, I see data on the peripheral.
I see a huge increase in throughput between an iOS central - iOS peripheral. I don't see why Android central - iOS peripheral shouldn't work he same way. From my understanding, Android and iOS use the same chip.
Any reason the performance is so poor? Is there anything else I can do to improve throughput?
Please have a look at the MTU size. My experience:
Using a iOS central, the central automatically starts the MTU size negotiation with some large value. I think it is larger than 200 bytes.
On most Android devices I tested this does not start automatically but you have to start the MTU size negotiation by your app (central). If you do not do that, Android cuts your data into 20 byte pieces. This has big influence on your throughput.
I just try to use the recieve() method of the socket.
When I send the data of a short String,as "send data",for 100
times,the recieve() function performs well.
While I send the data of a long String,as "send data to the
client,send data to the client,send data to the client,send data to
the client,send data to the client,send data to the client,send data
to the client,send data to the client",for 100 times,the recieve()
function cannot performs well.
In android project:
It just recieved about 30 packets, that is, other 70 packets are lost when I use another computer to send pakcets. But I checked the recieve buffer size, it is adequate to contain 100 packets above.
It recieves all the 100 packets when I use the localhost address to test. And I used the capturing tool named wireshark to capture the packets and the capturing tool can capture all the 100 packets everytime.
So I can eliminate the possibility of that another computer did not send all the packets.So I included that the problem exists in the emulator.
The above two cases both happened in android project,so the recieve buffer size is the same,
why in the former it will lose packets??
In java project:It recieves all the 100 packets too.
If you need all the packets then you should use TCP/IP protocol. In UDP protocol you can lose data because it is doesnot support reliable connection like Tcp/ip. UDP is designed to be faster at transferring data at the cost of unreliable connection. Based on network routing the packets you received could also arrive in different order.
Emulator TCP/IP stack's udp buffer size is smaller than the computer. So if the UDP buffer fills up, the emulator TCP/IP stack starts dropping packet. The PC TCP/IP stack pushes data at faster rate which the emulator can't handle. You can try reducing the rate at sender end to avoid filling the emulator buffer or increase the receive socket buffer size of android app to higher value by using the SO_RCVBUF socket option.
I'm working on an Android Bluetooth project to send and receive data from a custom made hardware. I used Bluetooth Chat Sample in SDK as basis.
I am sending data from one device to another (LG Nexus 4). All is ok until I reach a length of 1004 bytes (it is the audio data). At that point it splits it into 2 messages of 990 and 14 bytes in most of cases. but is strange sometimes its sending 1004 without splitting (approx. 4 times in 100).
I am sending this packet of 1004 bytes, in which there is 4 bytes is my header and rest of 1000 bytes is actual data which I want to use as per command in header, now if packets are splitting as per above mentioned way than I cannot handle the flow.
So, please let me know why packets are splitting in such way and how can I stop this splitting or, if I cannot do this, than please suggest me any alternative way to do this.
Thanks.
Data sent via Bluetooth socket is abstracted as a stream. Here the transport layer is broken into packets, where packet has a maximum size of almost 1KB(1000 bytes). So you can devise a mechanism in which you can send the message length info in the header, then on receiving side you will have to make subsequent read() calls; each returning data for one packet.