How to update advertising data dynamically using BLE Advertising library

Introduction

The question of how to update advertising data dynamically using the BLE Advertising library has been raised many times by our forum-goers, especially after the release of nRF5 SDK v15.2.0, in which the ble_advertising_advdata_update function was added. The usage of this function changed with the release of nRF5 SDK v16.0.0, and this blog post aims to shed light on this change, and make it clear how to use this function with SDK versions from both before and after the change.

Intention and revision

Prior to the release of nRF5 SDK v15.2.0 the primary way to change advertising data at runtime was to first stop the advertising, configure with new data, and then restart the advertising. This is a sure-fire but tedious way of changing the advertising data. The intention behind the introduction of the ble_advertising_advdata_update function was that the user would no longer have to stop-configure-start the advertising every time the advertising data should change, but rather just provide the new data and be done with it. While the initial release of the function in nRF5 SDK v15.2.0 achieved this, it was not specified thoroughly in the API Reference that the advertising data buffer provided to the ble_advertising_advdata_update function needed to be a new buffer - i.e it would not succeed if the user kept passing the the same data buffer, even though its contents had been updated. This meant that the user would have to do double buffering manually, and alternate between which advertising data buffer that was currently in use, and which could be updated.
This was a point of confusion for many users, which led to the changed implementation in nRF5 SDK v16.0.0.
In nRF5 SDK v16.0.0 the ble_advertising_advdata_update function was augmented to take care of the double-buffering issue.

This change can be seen in the following snippets from the functions source code.

Initial ble_advertising_advdata_update function as part of nRF5 SDK v15.2.0 and v15.3.0:
ret_code_t ble_advertising_advdata_update(ble_advertising_t  * const p_advertising,
                                          ble_gap_adv_data_t * const p_new_advdata_buf,
                                          bool                       permanent)
{
    if (permanent)
    {
        memcpy(&p_advertising->adv_data, p_new_advdata_buf, sizeof(p_advertising->adv_data));
        p_advertising->p_adv_data = &p_advertising->adv_data;
    }
    else
    {
        p_advertising->p_adv_data = p_new_advdata_buf;
    }

    return sd_ble_gap_adv_set_configure(&p_advertising->adv_handle,
                                        p_advertising->p_adv_data,
                                        NULL);
}


Revised ble_advertising_advdata_update function as part of nRF5 SDK v16.0.0 and v17.0.2:
ret_code_t ble_advertising_advdata_update(ble_advertising_t   * const p_advertising,
                                          ble_advdata_t const * const p_advdata,
                                          ble_advdata_t const * const p_srdata)
{
    VERIFY_PARAM_NOT_NULL(p_advertising);
    if (p_advertising->initialized == false)
    {
        return NRF_ERROR_INVALID_STATE;
    }

    if ((p_advdata == NULL) && (p_srdata == NULL))
    {
        return NRF_ERROR_NULL;
    }

    ble_gap_adv_data_t new_adv_data;
    memset(&new_adv_data, 0, sizeof(new_adv_data));

    if (p_advdata != NULL)
    {
        new_adv_data.adv_data.p_data =
            (p_advertising->p_adv_data->adv_data.p_data != p_advertising->enc_advdata[0]) ?
             p_advertising->enc_advdata[0] : p_advertising->enc_advdata[1];
        new_adv_data.adv_data.len = adv_set_data_size_max_get(p_advertising);

        ret_code_t ret = ble_advdata_encode(p_advdata,
                                            new_adv_data.adv_data.p_data,
                                            &new_adv_data.adv_data.len);
        VERIFY_SUCCESS(ret);
    }

    if (p_srdata != NULL)
    {
        new_adv_data.scan_rsp_data.p_data =
            (p_advertising->p_adv_data->scan_rsp_data.p_data != p_advertising->enc_scan_rsp_data[0]) ?
             p_advertising->enc_scan_rsp_data[0] : p_advertising->enc_scan_rsp_data[1];
        new_adv_data.scan_rsp_data.len = adv_set_data_size_max_get(p_advertising);

        ret_code_t ret = ble_advdata_encode(p_srdata,
                                            new_adv_data.scan_rsp_data.p_data,
                                            &new_adv_data.scan_rsp_data.len);
        VERIFY_SUCCESS(ret);
    }

    memcpy(&p_advertising->adv_data, &new_adv_data, sizeof(p_advertising->adv_data));
    p_advertising->p_adv_data = &p_advertising->adv_data;

    return sd_ble_gap_adv_set_configure(&p_advertising->adv_handle,
                                        p_advertising->p_adv_data,
                                        NULL);
}

Notice how the updated function takes care of the double-buffering issue of its predecessor by always populating a new data buffer before encoding and configuring the data.

Super short summary

nRF5 SDK v15.2.0 and v15.3.0:

Create two advertising data buffers, and alternate between which is passed to ble_advertising_advdata_update and which is updated.
To continuously update the advertising data follow the steps below:

1. Create two advertising data buffers
2. Fill the first data buffer with contents, and use it to configure and start advertising.
3. Update the second advertising data buffer with new advertising data.
4. Pass the newly updated second advertising data buffer to the ble_advertising_advdata_update function.
5. Update the first advertising data buffer with new advertising data.
6. Pass the newly updated first advertising data buffer to the ble_advertising_advdata_update function.
7. Repeat steps 3 - 6.

nRF5 SDK v16.0.0 and v17.0.2:

1. Create a single advertising data buffer.
2. Fill the data buffer with contents, and use it to configure and start advertising.
3. Update the advertising data buffer with the new advertising data.
4. Pass the updated advertising data buffer to the ble_advertising_advdata_update function.
5. Repeat steps 3 - 4.


Simple demonstration

Lastly, a simple project is attached to the end of the blogpost that demonstrates how the advertising data can be changed dynamically. The demonstration is made for a nRF52840 DK for use with the S140 SoftDevice and nRF5 SDK v.17.0.2. The included project is based on the BLE peripheral template example from the nRF5 SDK v17.0.2, and is modified to change its advertising data every 5th second. The device advertises as ‘Nordic_template’ and starts out with the default advertising payload from the example, before changing its advertising payload to contain a single letter in its manufacturer specific data field. I will leave the creative part of populating this manufacturer specific data with something cool to you!

Try it out yourself to see how it works, and please do not hesitate to open a ticket on DevZone if you should encounter any issues or questions! :)

ble_app_update_advertising_data.zip
  • Hello Mike,

    Thank you for saying that, I am glad to hear that you found the tutorial useful! :) 
    I am not sure I quite understood your issue from the initial description, but you will have to account for the 2 bytes of overhead that is added to every datafield in regards to the payload length that you pass to the update / encode function.
    So, if you pass a 31 byte payload directly, with an advertising that contains 3 fields - flags, name, and manufacturer specific datafield - (3 x 2 bytes overhead) your device name will be truncated by 6 bytes.

    It sounds like your issue has been resolved for now, but please do not hesitate to open a ticket in DevZone if you require further technical support! :)

    Best regards,
    Karl

  • Answered my own question now. I believe it will cope with the situation I was looking at. I needed to reset the adv_data.len to it's maximum before calling adv_data_encode

  • Hi Karl, great tutorial. I have a question about changing the manufaturer specific data length. In my application I want to increase the length of the manufacturer specific data field by 2 bytes after a certain point in time. My advertising data consistes of Flags, a dynamically changing manufacturer specific data field and a name field. The maximum length of all this is less than 31 bytes. I've found that when increasing the length of the manufacturer specific data and using ble_advdata_encode, the manufaturer data is handled correctly but the total length of the advertising data doesn't increase, leading to a truncated name field. I am passing a length of 31 bytes with my ble_gap_adv_data_t signal (unless that is being changed by another process I don't see)  Would the Update_advertising_data handle this case
    Thanks
    Mike

  • hello everyone,

    i want to use this example for 15.3 SDK but its not working so anybody have any suggestion please let me know.

    thanking you.

  • Are you using breakpoints in your code? Please know that the SoftDevice will assert immediately upon resuming the code execution after a breakpoint has been hit, since it will have missed all its timing critical deadlines that passed while the CPU was halted.

    Other than that the only thing I can recommend you is that you make sure to have DEBUG defined in your preprocessor defines, like shown in the included image:

    This will make your logger output a detailed error message whenever a non-NRF_SUCCESS error code is passed to an APP_ERROR_CHECK.
    Check the API reference for the function that returned the error code to find out why it was generated, and what you will need to do to resolve it.

    If this does not work, or if you require more technical support after this, please open a ticket in the forum instead so we may better help you resolve your issues.

    Best regards,
    Karl