How to correctly account for ATT MTU size in peripheral/central?

If I understand correctly, ATT MTU size negotiation is managed by CoreBluetooth automatically and transparently. (Just in case - I'm not talking about Bluetooth 4.2 PDU size negotiation here - as far as I know, it is rarely supported).


I'm not sure if / how to adjust my code to account for the actual MTU size to make communication as efficient as possible.


Let's say, I want to implement duplex serial-like data exchange between two devices (at least iOS 8). As I have seen, common approach (and maybe the best one) is to have two characteristics on the peripheral : one characteristic is "notifiable", the other one - writable. The peripheral sends data to the central through notifications, the central sends data to the peripheral through the writable characteristic.


The most technically useful answer that gave useful hints and highglighted possible caveats was http://stackoverflow.com/a/20321542/217823 by Justin Middleton. Is this even today, after 4 years, the most efficient way to implement BLE communication using CoreBluetooth?


I have seen a suggestion to use central.maximumUpdateValueLength on the peripheral to avoid pushing more data than the central can accept. Is this value affected by the ATT MTU size? Is there any similar way for the central side also to determine actual current MTU size when writing the data to the peripheral's writable characteristic? Or I should write in safe chunks of 20 bytes and ATT subsystem will automatically collect MTU-sized messages?


Also, I have heard that on some implementations you have to add a short sleep() after the first connection to give ATT subsystem some time to negotiate MTU sizes. Is this necessary for CoreBluetooth or does it happen automatically and I won't receive connection delegate calls sooner than MTU size negotiation was complete?

Accepted Reply

It's really strange that noone could respond to this. The answer turned out to be pretty simple, although not well documented: I had to use maximumWriteValueLengthForType, which is available since iOS 9 and, unfortunately, not documented in Apple's API portal but at least it was documented in Obj-C headers.


https://developer.apple.com/reference/corebluetooth/cbperipheral/1620312-maximumwritevaluelengthfortype

  • This doesn't seem to give the actual MTU size but rather simply returns the max value it could possibly be which is 512. I have tested this with a variety of MTU values set on my peripheral and iOS always returns 512 for this value. The only way I was able to get the actual MTU size in my iOS app was to add a read characteristic to my BLE protocol that is the MTU size and then on my peripheral, when it gets the onMtuChanged callback, set the negotiated MTU value on the MTU read characteristic. Then in iOS, before I need to know the MTU size, I read this characteristic and get the actual MTU value. This gives me a correct value for the MTU.

    I think this is a ridiculous solution/hack since CoreBluetooth is obviously getting the onMtuChanged callback just not making it available to the delegate. What's the point of a delegate if you don't give it all the information?!?!

Add a Comment

Replies

I'll try to rephrase it simpler with a hope that someone might be able to help:


On iOS, we don't have control over ATT MTU (no methods like `requestMtu` on Android) and we can only find out current MTU size on the peripheral side using `central.maximumUpdateValueLength` (since iOS 7).


As I understand, this value is available only on peripheral side and I can use it to adjust my characteristic updates when sending notifications.


But what should I do on the central side when writing to a characteristic? How do I know what amount of data I can write at once?


It would be inefficient pushing data by default 20 byte packets if the central and the peripheral have already negotiated larger MTU size. But how can I find it out on the central side?


If there is any person reading this, who have been successfully written more than 20 bytes at once from the central side, I would be really grateful to hear from you - how did you know what exact MTU value to use when writing data to a peripheral's characteristic?

It's really strange that noone could respond to this. The answer turned out to be pretty simple, although not well documented: I had to use maximumWriteValueLengthForType, which is available since iOS 9 and, unfortunately, not documented in Apple's API portal but at least it was documented in Obj-C headers.


https://developer.apple.com/reference/corebluetooth/cbperipheral/1620312-maximumwritevaluelengthfortype

  • This doesn't seem to give the actual MTU size but rather simply returns the max value it could possibly be which is 512. I have tested this with a variety of MTU values set on my peripheral and iOS always returns 512 for this value. The only way I was able to get the actual MTU size in my iOS app was to add a read characteristic to my BLE protocol that is the MTU size and then on my peripheral, when it gets the onMtuChanged callback, set the negotiated MTU value on the MTU read characteristic. Then in iOS, before I need to know the MTU size, I read this characteristic and get the actual MTU value. This gives me a correct value for the MTU.

    I think this is a ridiculous solution/hack since CoreBluetooth is obviously getting the onMtuChanged callback just not making it available to the delegate. What's the point of a delegate if you don't give it all the information?!?!

Add a Comment

Hi prognars,

I'm searching like you a method to requestMtu in iOS, but I did not found anything !

But I can send send WriteRequest with more than 20 bytes in one packets, I can send 155 bytes, I retrieve this value (155) after sniffing the connection between the iPhone (Central) and my peripheral, I saw that the iPhone triggered automatically an ATT MTU nogociation with a value of 158, as my peripheral supports this value, It accepts, and use it as ATT_MTU value.

It's too bad not to have like in Andoid a method to call with a desired ATT MTU value.

Hi prognars,

Im having the same problem with my app.. I would like to send packets in the most efficient way possible. First of all and after 3 years could find a solution ? I was wondering to have a readable characteristic in the peripheral with the value of doing central.maximumUpdateValueLength, and from the central I will be able to have both MTUS, the central one by reading that characteristic from the peripheral and the peripherals by doing peripheral.maximumWriteValueLengthForType. This way my MTU will be the smallest value
  • The call central.maximumUpdateValueLength is only valid if you are using the iOS app as a peripheral. In most cases this is not what is happening and therefore there is only a call to peripheral.maximumWriteValueLengthForType which for me ALWAYS returns 512 regardless of the actual MTU being set on my peripheral. The only way I was able to get the actual MTU size in my iOS app was to add a read characteristic to my BLE protocol that is the MTU size and then on my peripheral, when it gets the onMtuChanged callback, set the negotiated MTU value on the MTU read characteristic. Then in iOS, before I need to know the MTU size, I read this characteristic and get the actual MTU value. This gives me a correct value for the MTU.

  • @lehrian is correct. You cannot count on  peripheral.maximumWriteValueLengthForType to tell you what the central will allow. This just tells you the most that the peripheral allows. The central may or may not support that value, and unfortunately I know of no known API to get that value. I use two techniques: (1) look at the largest value successfully read from a characteristic. (2) Repeatedly try and write different value lengths from  peripheral.maximumWriteValueLengthForType down to 23 (the default MTU size without header) and see which is the first one that succeeds without error.

Add a Comment