Increasing speed on an OTA transaction
I'm trying to get the most speed I can for upgrading a BLE peripheral's firmware.
I'm using an OtaTransaction object to manage each send(around 100 write_without_response) between ack responses.
I've also called SetConnectionPriority(HIGH) before beginning the send.
I've also tried increasing the MTU to the highest supported by the peripheral (68), but the android device can only send at an MTU of 20(even though it negotiates the MTU at 69 which is clearly wrong.)
Is there any more ideas you can give me to how to increase the transfer speed?
I've been trying to fit as many packets as I can into each connection event, following this article: https://punchthrough.com/maximizing-ble-throughput-on-ios-and-android/
I think the problem might be related to write_without_response.
I checked wireshark and it is using Write Command (0x52) which seems to be correct, it's method is 'Write Request' (0x12) though. Is that normal?
I've captured some packets over wireshark using the nrf connect app vs using our app with sweetblue. I can see some timing differences which I'm guessing are the real cause for the slow down(it just being slow to begin with).
Using write_command I'm trying to write at 6 packets at a time, and with the nrfconnect app you can see these come through one after the other, roughly 1 ms between. When I'm writing 20 packets at a time with the app you can see there being larger time differences between each packet with HCI_EVT packets in between.
ryanbis last edited by ryanbis
Looks like you're doing everything obvious that you can at this point. When requesting the MTU, what value are you requesting? 68? As a habit, I've always requested a higher amount than I know the device can handle (as setting the MTU is a negotiation anyway). If you're getting 69, that's coming from the device (or android is messing something up internally). I would look into the firmware a bit to see what's going on with the MTU negotiation. I haven't found any way to affect this on the phone side.
One last thing you can do to increase speed, is to lower the update rate for SweetBlue's internal update loop. The default is about 20ms. You can do this like so:
BleManagerConfig config = new BleManagerConfig(); config.autoUpdateRate = Interval.millis(1); BleManager mgr = BleManager.get(this, config);
You can always update the config later, and set it back on the manager instance using BleManager.setConfig(). I would suggest putting the loop back to 20ms when done with the OTA.
EDIT: I also find that article highly suspect with regards to the numbers they show for android. From our experience, bluetooth has always been faster on iOS. Android may have the capacity to have quicker transfers, but in practice, it's has ALWAYS been much slower, and buggier (hence the need for SweetBlue, and also why there is no SweetBlue for iOS).
@ryanbis Thanks Ryan.
I tried altering the 'auto update rate' but it didn't help unfortunately... Would it have been inserting unnecessary delays into callbacks or not necessarily?
One thing I noticed with the nordic lib is they do their queuing on the main thread, is it possible to have this as an option for high priority or compatibility reasons? I'm running out of ideas as to why it works so much faster in the nrf connect app than our app.
Also, In regards to your question about the MTU, I was using 68 since it's the peripheral's max however i was sometimes getting higher values even with other devices so I had to cap the value returned and use 68 instead. The manufacturer of the Amobile device I am using confirmed with me that it can only do an MTU of 23 so I'm stuck with the lower MTU size.
Hmm, you should have seen at least a slight improvement. You saw no difference between the calls? Looks like your original log, there was about 20ms between each send, which would account for the update tick.
It's possible to put everything on the main thread, though I don't think you'll get a speed increase (typically there's a lot going on in the main thread). It could be thread switching...the callbacks are posted to the main thread. You can shut that off via
I'd suggest looking through the options in the manager config class here https://api.sweetblue.io/com/idevicesinc/sweetblue/BleManagerConfig.html
@ryanbis thanks Ryan.
Maybe altering the update loop helped but i just expected it to increase the speed more dramatically.
A nice thing I noticed by writing to the characteristic via the android api is you can buffer the writes until it returns false.
I managed to get a speed increase by doing this on the main thread whenever I intercepted a characteristic read/write event. If I leave these callbacks on the main thread will they not thread switch by the time I receive them? I'm assuming android api calls them on the main thread.
Hah, what android does with callbacks is different depending on OS level, and manufacturer. Some post the callbacks to main, some are on separate threads, hence why SweetBlue posts them all on the main to be consistent.
However, I'm not really sure of what you're actually doing. What do you mean by "whenever I intercepted a characteristic read/write event."?
@ryanbis Ok, that's nice of android to post back on different threads Good thing you are making is consistent at least.
I mean't that I am using the device read/write listener to listen for when an OTA characteristic has been written without response, then I keep writing data to the characteristic(not waiting for the callback) until it returns false, in which case I can't keep buffering the data due to the buffer being full, so I wait for the next callback/readwrite event to do it again.
You can see from this wireshark screenshot using this method is much better now, I'm getting packets sent in bulk way more regularly. I managed to get the transfer down to 5 minutes for roughly 180k.
@ryanbis Do you think you could give me access to a onCharacteristicWritten callback(without any thread switching) for the device? This way I'd be able to have the writeCharacteristic/onCharacteristicWritten on a tight loop for OTA. Perhaps you could have a BluetoothGattCallback proxy object which is setable on the device for people which would like to use the native stack callbacks for certain things?
On another note, do you increase the priority of the thread/looper which the OtaTransaction runs on?
If you set BleManagerConfig.postCallbacksToMainThread to false, it will just post the callback on whatever thread it came in on from the android system.
There is no mechanism to increase thread priority. Usually this just ends up causing problems, and doesn't really give much performance improvement. All update calls are run from the SweetBlue thread. If you want to decrease the tick time between update calls, you can set BleManagerConfig.autoUpdateRate.
@ryanbis Oh ok, good to know there is no thread switching when it's set to false. I did end up leaving it set to false, and it did seem to run faster. I agree about the thread priority, it didn't seem to help me in the end, I think at the least i'd have to increase the priority of the bluetooth service as well which seems impossible other than from a process level when the device is rooted.
So I guess the update calls are any calls going out which the autoupdaterate regulates/and is ran on the sweetblue thread, where as any callbacks are not put onto this thread and are just logged and pushed through to callbacks in use?
Things are running pretty good now that I'm writing directly to the characteristic, I also disabled the logging temporarily during the transfer to squeeze more performance.
Very interesting findings. @benp, do you think you could make an example Nordic DFU Android project for the community to reference to?
@alejandrohcruz I used macros from within the rfConnect app to test the upgrade speed, I didn't actually write any code. I'm probably going to post up the java code I wrote to work with sweetblue once I'm done ironing out any remaining issues, we are currently using xamarin for our app but I had to do the ota code in java to improve the speed.
Interesting, thanks for the info. Good luck and looking forward to that Java code. I will try to do the test myself on code and see what I get. By the way, did you try to do a test with the SweetBlue Toolbox app? I guess it should be possible to perform an OTA transaction with it.
@alejandrohcruz I didn't see a way to use macros with the toolbox app so didn't end up using it.
I've attached a zip of the java files I used for doing the OTA transaction.
@ryanbis It also includes the wrappers I needed for callbacks to work in xamarin(it doesn't support callbacks which make use of java generics).
@benp thanks a lot, will check it out!