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
  • This is a good example of a project that I am currently working on.  I could use some clarifications in the function "adv_data_update_timer_handler."  1. You define PAYLOAD_SIZE as 1 but there are 6 uint8_t types of data in the actual list.  Is this a typo?  2. There is a static variable defined payload_index in the function and initialized to zero. Later in the function you test that variable against a defined TOP_INDEX which equals 5.  Can you explain how the payload_index will ever increment to the TOP_INDEX level because of being set to zero upon entry into the function?

  • Hello rodims,

    I am happy to hear that you found the blogpost and example useful!
    You are correct that the expected behavior of the example is to drop the device name as well when it starts its updating manufacturing specific data operation. If you look into the adv_data_update_timer_handler function, it overwrites the entire advertising payload with the new_advdata advertising data, which is only populated by the manufacturing specific datafield with a single character - not very imaginative, but it is only meant as an illustration for how the dynamic updates could work more than an actual use-case.

    If you would like the advertising data to include a name you will have to provide this to new_advdata, or change the adv_data_update_timer_handler to also update the device name.

    As mentioned in my previous comments here on the blogpost, please open a DevZone ticket if you require support or have any questions, as the blogpost comments are checked at a less-frequent interval.

    Best regards,
    Karl

  • Hi Karl,

    after having stumbled over the implementation for updating advertising information in SDK 15.3 I was glad to find this explanation and demonstration for the new SDK 16.0 implementation of ble_advertising_advdata_update. That's exactly what I need.

    Before adding this to our project I downloaded your example code to test it on a nRF52840 PDK and nRF-Connect on my Android smartphone. (Android 10, also tested with another phone with Android 8).
    I placed the sample code into the examples folder of a Android 17.0.2 SDK, and I'm using SES (Segger Studio) v 5.64

    My first impression was that it did not work at all, because I was looking for the original advertising name "Nordic_Template", but did not find it in nrfConnect.  But after making some minor changes for seeing NRF_LOG messages with RTTview and a bit of debugging I found out the reason. The advertised Device name is correct at startup, i.e. after a Reset ( to be sure, I do not only test this after programming with SES, but explicitly issuing a Reset with SES.
    Whenever the Reset is issued, nRFConnecgt will show the correct Device name Nordic_Template. As soon as the timer expires and the first ble_advertising_advdata_update is invoked, the name in nRFConnect is "lost" and replaced by the typical "N/A (Nordic)" display.
    Note that the dynamic update of the ManufacturerSpecific Data works correctly as expected, i.e. for each timer event nRFConnect will show the updated information. This is good, but not at the cost of the wrong / missing device name during advertising.

    Before I start debugging this in detail myself, I would like to know whether it is expected behavior for this example to loose the device name ??
    In my application I definitely need correct advertising for all the information, including the device name.

    I add two screen shots of nRFConnect, the first one shows the "correct" information after a Reset of the nRF52840, the second one shows the "wrong" information after the first timer update with ble_advertising_advdata_update is excecuted.  This remains wrong for all subsequent timer events.
    Note that I changed the name from Nordic_Template to DX-1234.

  • Hello Ken,

    Thank you for saying that!
    I am happy to hear that you found the blogpost and demonstration useful for your application :)

    I hope you have a great day!

    Best regards,
    Karl

  • Hi Karl

    Having looked through the many posts where everything was going wrong with advertising update, your demo worked perfectly first time.  I had been using the stop/update/start method for an update to live data within the advertising and scan response packets, but this method lets me use the new advertising module sequencing and have live updates without compromising the timing!

    Excellent example
    Many thanks

    Ken