BLE Fragmentation data loss

I am using an nRF52840 processor within a Particle Argon IoT module. I am polling a third party BLE peripheral (Shelly relay) for status data once a minute. The data packet that device responds with is 779 bytes long. I am consistently losing 3 bytes of that data.

Using a PC and its BLE, I poll for the exact same data from the peripheral BLE device. I have confirmed that the 3 bytes lost by the Argon device are the 245th, 490th, and 735th bytes. In other words, after the first sent packet, the first byte between each fragment is being lost.

I've reported this to Particle, and they pointed out that the fragmentation is dealt within the Nordic SoftDevice software.

Is this a known Nordic problem? 

The Particle OS does have a BLE software component to it which I believe could potentially be the source to this problem. 

Any insight would be appreciated.

Parents
  • Hello,

    The easiest way to troubleshoot this would likely be by capturing a bluetooth sniffer trace and compare the packet exchange when it works and when it doesn't. Our nRF Sniffer for Bluetooth LE is a low cost option, but it requires that you have a nRF52840 Dongle or one of the other supported boards laying around. Is the code to poll the sensor provided by Particle or is it something you have written. Either way, it would be nice to see the code showing how it is implemented if possible. I'm not aware of any known bugs in the stack that could explain the data loss.

    Best regards,

    Vidar

  • Here's their code (Particle). I call this member function getValue() four times in rapid succession asking for 244 bytes of data. I get 3 full packets of 244 bytes, and one final packet of 44 bytes for a total of 776 bytes. The message is supposed to be 779 bytes long. When I examine that data I see that the 245th, 490th, and 735th bytes are missing. (BLE_MAX_ATTR_PACKET_SIZE is 244).

    ssize_t BleCharacteristic::getValue(uint8_t* buf, size_t len) const {
        if (buf == nullptr || len == 0) {
            return SYSTEM_ERROR_INVALID_ARGUMENT;
        }
        len = std::min(len, (size_t)BLE_MAX_ATTR_VALUE_PACKET_SIZE);
        if (impl()->connHandle() != BLE_INVALID_CONN_HANDLE){
            return hal_ble_gatt_client_read(impl()->connHandle(), impl()->attrHandles().value_handle, buf, len, nullptr);
        }
        else if (impl()->isLocal()) {
            return hal_ble_gatt_server_get_characteristic_value(impl()->attrHandles().value_handle, buf, len, nullptr);
        }
        return SYSTEM_ERROR_INVALID_STATE;
    }
     
    I don't know if hal_ble_gatt_client_read is your SoftDevice layer being called, but I think so. Is there something special that needs to happen for fragmented packages?
  • Thank you for reporting back and providing an explanation to the issue you observed. As you may know, the default att MTU for BLE devices is always 23 bytes. The longer MTU in this case is negotiated between the devices after the connection is established. The payload for a read response can be up to MTU - 1 byte ATT header while for a write or read request it is MTU - 3 bytes.

    Softdevice API documentation for future reference

    sd_ble_gattc_read()

    GATTC Characteristic or Descriptor Value Read

  • Hallo Vidar,

    any chance I can get a copy of the nRF52840 SoftDevice API doc?

    Also, do you have an opinion on how such a discrepancy could arise (other than a direct "it was set wrongly")? Is there something in the fragmentation algorithm that could explain this?

  • Hi, 

    Unfortunately, we don't have an offline version of the SD API documentation available. I think it's difficult to speculate what the root cause of this issue without some debugging or code review. This is why I initially suggested capturing sniffer trace to analyze the packet exchange on-air. This allows you to verify if the read requests and responses are as expected. If they are, you would know that the bytes are being lost at the application level. 

  • Vidar,

    When you say the payload for a read response can be up to MTU - 1, does that mean if the negotiated MTU is 247 and the peripheral is sending a response large enough to require fragmentation it's possible to get data fragments (payload) of 246 or less? (I.e. 245,244)? This would certainly explain my problem since the Particle OS layer does not allow packets greater than MTU - 3.  

  • Hallo,

    Yes, with a MTU of 247 bytes, you can receive a read response with up to 246 bytes of payload. Regarding fragmentation, it seems like it must be handled at the application layer on both devices. The maximum attribute value length in a bluetooth characteristic is 512 bytes, which makes it impossible to receive the full 779 bytes of data in one read, even with a long read. 

    I also had a look at the hal_ble_gatt_client_read() implementation and see that it only issues "ATT_READ_REQ" requests (i.e. it does not perform multiple read request with offsets). The response to this request is ATT_READ_RSP (notice that there is only 1 byte overhead in this response):

      

    ttps://github.com/particle-iot/device-os/blob/507ab1517a8720af4e3bd20a6c6cc66f81cfc1f1/hal/src/nRF52840/ble_hal.cpp#L3239 

Reply Children
Related